演算子の高度な使い方

| Topic path: Top / 授業 / C言語基礎 / 演算子の高度な使い方

これまでの勉強で、演算子は必要に応じて少しずつ出てきました。

ここでは、演算子について、まだ説明していない部分を説明します。


*オペランド [#m77529e0]

演算の対象となる値や変数を''オペランド''といいます。

オペランドが1つの演算子を''単項演算子''、2つのものを''2項演算子''、3つのものを''3項演算子''といいます。


*増分・減分演算子の前置と後置 [#a54f1eaf]

単項演算子である増分演算子 ''++'' は、オペランドの値を1増やします。
同じく単項演算子である減分演算子 ''--'' は、オペランドの値を1減らします。

増分・減分演算子は、演算子をオペランドの前に置くこと(前置)も、オペランドの後に置くこと(後置)もできますが、実は、振る舞いが異なります。

''前置''のとき、増分・減分演算子を含む式や命令文は、増分・減分演算子が適用されてから評価されます。

''後置''のとき、増分・減分演算子を含む式や命令文は、増分・減分演算子が適用される前に評価され、それから増分・減分演算子が適用されます。

次のプログラムは、増分演算子を前置と後置で使用しています(プログラム1)。
#geshi(c){{
  int i = 0;

  printf("%d\n", ++i);
  printf("%d\n", i++);
  printf("%d\n", i);
}}
最初のprintf関数は、前置の増分演算子なので、先に増分演算子が適用され、その後にprintf関数が実行されます。
次のprintf関数は、後置の増分演算子なので、先にprintf関数が実行され、その後に増分演算子が実行されます。

したがって、このプログラムを実行すると、次のようになります。
#geshi(sh){{
luna% a.out
1
1
2
}}

**演習1 [#e25f652b]
プログラム1を作成し、実行結果を確認せよ。


*ビット単位の演算子 [#jc89f6fc]

データは、すべて2進数のビット列で表されています。
そこで、整数を表す2進数のビット列に対してビット単位で演算する演算子が用意されています。
|優先順位|演算子|使用例|意味|h
|2|˜|˜a|補数|
|6|<<|a << b|左シフト|
|~|>>|a >> b|右シフト|
|9|&|a & b|ビット論理積|
|10|^|a ^ b|ビット排他的論理和|
|11|&#x7c;|a &#x7c; b|ビット論理和|

補数以外の演算子には、複合代入演算子 <<=, >>=, &=, ^=, |= もあります。


**補数 [#be12c9b2]
補数演算子 ''&tilde;'' は、すべてのビットを反転させます(プログラム2)。
#geshi(c){{
  int i = 0x80000010;

  int j = ~i
  printf("%08x\n", j);
}}
#geshi(sh){{
luna% a.out
7fffffef
}}


**左シフト、右シフト [#u4be1562]
左シフト演算子 ''<<'' はビット列を指定された数だけ左にシフトします。
1ビット左にシフトするごとに、値は2倍になります。

右シフト演算子 ''>>'' はビット列を指定された数だけ右にシフトします。
1ビット右にシフトするごとに、値は半分になります。

左オペランドが符号なし整数または符号付き整数で先頭ビットが 0(値が 0 または正)のとき、すべてのビットがシフトし、シフトによってはみ出たビットは無視され、空いたビットは 0 になります。
左オペランドが符号付き整数で先頭ビットが 1(値が負)のとき、どう処理するかは処理系に任されています(プログラム3)。
#geshi(c){{
  unsigned int ui = 0x00000010;
    signed int si = 0x80000010;

  printf("%08x, %08x\n", ui, si);
  printf("%d, %d\n", ui, si);

  ui = ui << 1;
  si = si << 1;

  printf("%08x, %08x\n", ui, si);
  printf("%d, %d\n", ui, si);
}}
#geshi(sh){{
luna% a.out
00000010, 80000010
16, -2147483632
00000020, 00000020
32, 32
}}




**論理積 [#i62e1f15]
ビット単位の論理積演算子 ''&'' は、ビットごとに論理積を取ります。
論理演算子の論理積演算子 ''&&'' と間違えないよう注意しましょう。

1 と論理積を取るとそのビットは保存され、0 と論理積をとるとそのビットは0になります。
この性質を利用して、上位ビットや下位ビットだけを取り出す、マスク操作に用いられます(プログラム4)。
#geshi(c){{
  int i = 0xaaaaaaaa;
  printf("%08x\n", i);

  int j = i & 0x0000ff;
  printf("%08x\n", j);
}}


**論理和 [#y37b82de]
ビット単位の論理和演算子 ''|'' は、ビットごとに論理和を取ります。
論理演算子の論理和演算子 ''||'' と間違えないよう注意しましょう。

1 と論理和をとるとそのビットは 1 になり、0 と論理話をとるとそのビットは保存されます。
この性質を利用して、特定のビットだけを1にする操作に用いられます(プログラム5)。
#geshi(c){{
  int i = 0xaaaaaaaa;
  printf("%08x\n", i);

  int j = i | 0x0000ff;
  printf("%08x\n", j);
}}
#geshi(sh){{
luna% a.out
aaaaaaaa
aaaaaaff
}}



**排他的論理和 [#ja94dc04]
ビット単位の排他的論理和演算子 ''^'' は、ビットごとに排他的論理和を取ります。

1 と排他的論理和をとるとそのビットは反転し、0 と排他的論理和をとるとそのビットは保存されます。
この性質を利用して、ビット列を部分的に反転させる操作に用いられます(プログラム5)。
この性質を利用して、ビット列を部分的に反転させる操作に用いられます(プログラム6)。
#geshi(c){{
  int i = 0xaaaaaaaa;
  printf("%08x\n", i);

  int j = i ^ 0x0000ff;
  printf("%08x\n", j);
}}
#geshi(sh){{
luna% a.out
aaaaaaaa
aaaaaa55
}}

また、2つ以上のビットの排他的論理和を求めると、ビットに含まれる 1 の数が奇数のとき 1、偶数のとき 0 となる性質を利用して、パリティ・ビットのチェックに用いられます(プログラム6)。
また、2つ以上のビットの排他的論理和を求めると、ビットに含まれる 1 の数が奇数のとき 1、偶数のとき 0 となる性質を利用して、パリティ・ビットのチェックに用いられます(プログラム7)。
#geshi(c){{
  int i = 1 ^ 1 ^ 1 ^ 0 ^ 0 ^ 0 ^ 0 ^ 0;
  printf("%d\n", i);

  int j = 1 ^ 1 ^ 0 ^ 0 ^ 0 ^ 0 ^ 0 ^ 0;
  printf("%d\n", j);
}}
#geshi(sh){{
luna% a.out
1
0
}}



**論理和 [#y37b82de]
ビット単位の論理積演算子 ''&'' は、ビットごとに論理積を取ります。
論理演算子の論理積演算子 ''&&'' と間違えないよう注意しましょう。

1 と論理和をとるとそのビットは 1 になり、0 と論理話をとるとそのビットは保存されます。
この性質を利用して、特定のビットだけを1にする操作に用いられます(プログラム7)。
#geshi(c){{
  int i = 0xaaaaaaaa;
  printf("%08x\n", i);

  int j = i | 0x0000ff;
  printf("%08x\n", j);
}}
#geshi(sh){{
luna% a.out
aaaaaaaa
aaaaaaff
}}


**演習2 [#oab755e9]

プログラム2からプログラム7を作成し、実行結果を確認せよ。



*論理演算子の短絡評価 [#ff7a051e]

論理演算子の論理積 ''&&'' は、いずれかが偽 (0) のときは結果が偽 (0) となり、そうでないときは結果が真 (1) となります。

そこで、論理積演算子は、左側のオペランドを先に評価し、結果が偽 (0) のときは、右側のオペランドを操作しないで結果として偽 (0) を返します。

同様に、論理演算子の論理和 ''&#x7c;&#x7c;'' は、いずれかが真(0 でない)のときは結果が真 (1) となり、そうでないときは結果が偽 (0) となります。

そこで、論理和演算子は、左側のオペランドを先に評価し、結果が真(0 でない)のときは、右側のオペランドを操作しないで結果として真 (1) を返します。

これを、論理演算子の''短絡評価''といいます。

この性質を利用して、除算を行う前に割る数が 0 でないことを確認したり、配列の要素にアクセスする前に要素番号が有効な範囲内にあることを確認するなど、実行時にエラーを起こさないようにするためのエラー・チェックに使うことがあります(プログラム8)。
#geshi(c){{
  int i = 0;

  (i > 0) && printf("正\n");
  (i > 0) || printf("0または負\n");
}}
#geshi(sh){{
luna% a.out
0または負
}}


**演習3 [#n8dc13ce]
プログラム8を作成し、実行結果を確認せよ。




*配列添字演算子と関数呼び出し演算子 [#n51d4a9c]

配列の添字を表すときに使う角括弧 ''[ ]'' と、関数を呼び出すときに使う丸括弧 ''( )'' も、実は演算子です。
|優先順位|演算子|使用例|意味|h
|1|[ ]|a[b]|配列添字|
|~|( )|a(b)|関数呼び出し|

演算子であることを意識する必要はありません。


*演算子の優先順位 [#i79e91b7]

これまでに勉強した演算子の優先順位をまとめておきます。
|優先順位|演算子|使用例|意味|h
|1|[ ]|a[b]|配列添字|
|~|( )|a(b)|関数呼び出し|
|2|+|+a|正の数|
|~|&minus;|&minus;a|負の数|
|~|!|!a|論理否定|
|~|++|i++|値を1増やす|
|~|~|++i|~|
|~|&minus;&minus;|i&minus;&minus;|値を1減らす|
|~|~|&minus;&minus;i|~|
|~|&tilde;|&tilde;a|補数|
|3|()|(b) a|キャスト|
|4|*|a * b|積|
|~|/|a / b|商|
|~|%|a % b|剰余|
|5|+|a + b|和|
|~|&minus;|a &minus; b|差|
|6|<<|a << b|左シフト|
|~|>>|a >> b|右シフト|
|7|<|a < b|小さい|
|~|<=|a <= b|小さいか等しい|
|~|&gt;|a > b|大きい|
|~|>=|a >= b|大きいか等しい|
|8|==|a == b|等しい|
|~|!=|a != b|等しくない|
|9|&|a & b|ビット論理積|
|10|^|a ^ b|ビット排他的論理和|
|11|&#x7c;|a &#x7c; b|ビット論理和|
|12|&&|a && b|論理積|
|13|&#x7c;&#x7c;|a &#x7c;&#x7c; b|論理和|
|14|?と:|a ? b : c|条件|
|15|=|a = b|代入|
|~|+=|a += b|加算代入|
|~|&minus;=|a &minus;= b|減算代入|
|~|*=|a *= b|乗算代入|
|~|/=|a /= b|除算代入|
|~|<<=|a <<= b|左シフト代入|
|~|>>=|a >>= b|右シフト代入|
|~|&=|a &= b|ビット論理積代入|
|~|^=|a ^= b|ビット排他的論理和代入|
|~|&#x7c;=|a &#x7c;= b|ビット論理和代入|


*その他の演算子(おまけ) [#g6a22182]

上の表にない演算子は、C言語応用で勉強する構造体、ポインター、アドレスに関する演算子と、特に用途がない順次演算子だけです。

|優先順位|演算子|使用例|意味|h
|1|.|a.b|構造体の要素選択|
|2|->|a->b|構造体ポインターの要素選択|
|~|*|*a|ポインター参照|
|~|&|&a|アドレスへのアクセス|
|16|,|a, b|順次|

この他には演算子はありません。


*2進数のビット列を作る(おまけ) [#gacf2a53]

C言語では、16進数では出力できますが、2進数では出力できません。

そこで、2進数のビット列を表す文字列を作成するプログラムを考えてみましょう。
ここでは、対象を8ビットの文字としています(プログラム9)。
#geshi(c){{
  char c = 'A';
  char s[9];

  int i;
  for (i = 0; i < 8; i++) {
    int k = (c >> i) & 0x01;  // iビット右にシフトさせて、最下位のビットだけを取り出す
    s[8 - i - 1] = '0' + k;  // 要素番号 8 - i - 1 の要素に値 k を表す文字を代入する
  }
  s[8] = '\0';  // 文字列の最後にナル文字を代入する

  printf("%s\n", s);
}}
#geshi(sh){{
luna% a.out
01000001
}}


*負の符号付き整数をシフト演算で2倍や1/2倍する(おまけ) [#yb8a9ccc]

負の符号付き整数をシフト演算で2倍や1/2倍するには、先頭ビットが 1 のとき(値が負のとき)、2の補数で表現されている数の絶対値を求めてシフトし、これを2の補数にします(プログラム10)。
#geshi(c){{
/*
 *  先頭ビットが1なら、2の補数の絶対値を求め、1ビット右シフトし、2の補数に戻して返す。
 *  先頭ビットが0なら、1ビット右シフトして返す。
 */
int half(int x) {
  return x >> 31 ? ~(~(x - 0x00000001) >> 1) + 0x00000001 : x >> 1;
}


int main(void) {
  int i = 0x80000010;  // 1000 0000 0000 0000 0000 0000 0001 0000
  printf("%08x\n", i);
  printf("%d\n", i);

  int j = half(i);
  printf("%08x\n", j);
  printf("%d\n", j);

  return 0;
}
}}
#geshi(sh){{
80000010
-2147483632
c0000008
-1073741816
}}

2の補数から絶対値を求める方法や2の補数を求める方法は計算機アーキテクチャーの授業でしっかりと勉強してください。
トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS