• 検索結果がありません。

演算

ドキュメント内 note.dvi (ページ 74-81)

第 6 章 C 言語入門

6.8 変数とは

6.8.5 演算

この結果は,次の3通りが考えられる.

1. y = 0これは,先に-を実行し, それからインクリメントをした.

2. y = -1これは,1 - 2を実行した.

3. y = 1これは,2 - 1 を実行した.

このように2項演算と各項の評価をどの順序で行なうかは,不定であるので注意すること24 . Example 6.8.10 次のような式を考えよう.

x+++y ;

この式は,x[+[++yと x++[+[yと2通りに解釈できるが, C の構文解析では,最大一致法をとるという規 約があり,そのため,構文に合致する最大のトークンである x++を採用し,x++[+[yと解釈される.

x+++++y

はx++[+[++yという解釈が可能であるが, Cの構文解析パーサにはx++[++[+[yと解釈することが求めら れている.

6.8.5.3 代入演算子 次のような代入を考える.

a += 1 ; a -= 1 ; a *= 1 ; a /= 1 ;

これらは,それぞれ,aの値を1 加えた(減らした,掛けた,割った)値を再び,aに代入する演算である.

Example 6.8.11 代入の例は以下のものである.

int a ; a = 1 ;

a += 1 ; /* a の値は 2 となる. */

その他にも, %=, <<=, >>=, &=, ^=,|=がある. もちろん, =も代入である(=を単純代入と呼び, それ以外 の代入演算子を複合代入と呼ぶ).

6.8.5.4 単項演算子

単項ビット演算には,~,!がある.

~は1の補数をとるための演算子で,被演算数は整数でなければならない. この時,整数の格上げが行な われる. 1の補数とはビット反転のことである. すなわち,以下のような操作を行う.

1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0

~

0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1

!は論理否定をとるもので,被演算数は算術型かポインタである. 被演算数が 0であれば,結果は1とな り, そうでなければ, 0である.

24このような評価式はANSI規約[3, X3010 6.3, p.1873]の「式」の規定で,「直前の副作用完了点から次の副作用完了点までの 間に,式の評価によってオブジェクトに格納された値を変更する回数は高々1回でなければならない.さらに,変更前の値は,格納さ れる値を決定するためだけにアクセスしなければならない.」とある.したがって,y = (x++) - (x++)が不定であるだけでなく,i

= ++i + 1も不定であることに注意しよう.

Id: C3.tex,v 1.17 2001-03-20 15:15:41+09 naito Exp

6.8.5.5 2項ビット演算

2項ビット演算には,&, |,^, <<,>>がある. これらの演算の被演算数は汎整数型でなくてはならない.

被演算数に対して,通常の(格上げ等を含む)算術変換が行われる. 汎整数型に関しては,内部表現を定め ていないが,通常の2進表現と考えてビット演算を行った結果と理解してよい.

&はビットごとのANDをとる演算子,|はビットごとのORをとる演算子,^はビットごとのXORを とる演算子である.

1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1

&

1 0 1 0 1 0 1 0 1 0 1 0 1 0 0 0

1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1

|

1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0

1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 1

^

0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1

<<,>>はそれぞれ,ビットごとの左シフト,右シフトをとる演算子である. 被演算数は汎整数でなくてはな らない. また,整数への格上げが行なわれる. 結果は,格上げを受けた左被演算数の型である.

E1 << E2の値は,E1を左に E2だけシフトしたものである. オーバーフローがない場合には 2E2 をか けることに等しい. E1 >> E2の値は,E1を右にE2だけシフトしたものである. E1が符号なし,または負 でない時には2E2 で割ることに等しい. そうでない時には,結果は処理系依存である.

E2が負である時には,結果は不定である.

また,左にシフトした時には,右には0がつめられる. 符号なし数を右にシフトした時には,左には0が つめられるが,負の数を右にシフトした時には,左には, 1がつめられる(算術シフト)か 0がつめられる

(論理シフト)かは処理系に依存する.

1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0

<<1

0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 0

0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1

>>1

0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 このように正の数のシフトには何の問題も生じない.

オブジェクトがsignedの場合 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0

>>1(算術シフト)

1 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1

1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0

>>1(論理シフト)

0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 負の数のシフトで,どちらが起きるかは処理系依存

Example 6.8.12 <<,>>の演算の例は,以下の通りである.

signed char a ; unsigned char b ; a = -0x02 ; b = 0x02 ;

a << 1 ; /* 結果は int で FFFFFFFC */

a >> 1 ; /* 算術 shift の時, 結果は int で FFFFFFFF, 論理 shift なら 7FFFFFFF */

b << 6 ; /* 結果は int で 128 */

a << 6 ; /* 結果は int で FFFFFF80 */

ここで,負の数のシフトは,整数への格上げを受け,符号拡張も受けていることに注意せよ.

signed char a = -0x02

Id: C3.tex,v 1.17 2001-03-20 15:15:41+09 naito Exp

1 1 1 1 1 1 1 0

符号拡張と格上げ 格上げ

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0

>>1(算術シフト) >>1(論理シフト) >>1

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1

0xFFFF 0x8FFF 0x0077

6.8.5.6 論理演算

論理演算には,比較(関係演算子),等値演算子, AND, OR がある. これらの演算子は全て,左から右に 適用される.

比較には,<,>, <=, >=があり,それが正しければ, int型の1,そうでなければint型の0が返される.

以下のようにして使う.

式1 < 式2

比較演算子,等値演算子は被演算数が算術型の時には,通常の算術変換を行う. 比較演算子は被演算数が 算術型の時,算術変換をした後,その型に適合した数値としての比較を行う.

等値演算子は,==で表される.

式1 == 式2

その結果が正しければ(式1と式2が等しい) 1,そうでなければ 0 となる. ただし, ==は内部表現で 判定しているので,

0xFFFF == 65535 ;

0xFFFFFFFF == -1 ; /* int が4バイトで, 2の補数表現の時 */

はともにint型の1となる.

等値演算子の否定は,!=で表される.

式1 != 式2

その結果が正しければ(式1と式2の値が等しくない)int型の1,そうでなければint型の0となる.

論理AND演算子は&&で, 式1 && 式2

であって,被演算数の両方が非零の時int型の1を返し,そうでない時int型の0を返す. また, &&で 評価されるのは,もっとも左の式であり, それが0 であれば,その式の値は 0である. そうでなければ,右 の被演算数が評価される. それが0 であれば,式の値は0となり,そうでない時に1となる.

論理OR演算子は||で, 式1 || 式2

であって,被演算数のどちらかが非零の時1 を返し,そうでない時0 を返す.

論理AND, OR演算子の結果はintである.

Example 6.8.13 論理AND, OR演算子の例は以下の通りである.

Id: C3.tex,v 1.17 2001-03-20 15:15:41+09 naito Exp

int a=0,b=1,c=2,d=3 ; (a < b)&&(c < d) ;

/* これは a < b && c < d でも同じだが, */

/* 関係をはっきりさせるために, 括弧をつけた方がよい. */

/* この式の評価は, (1) && (1) となるので, 1 が値となる */

(a > b)||(c > d) ;

/* この式の評価は, (0) || (0) となるので, 0 が値となる */

a && (c < d) ;

/* この式の評価は, (0) && (1) となるので, 0 が値となる */

b || (c > d) ;

/* この式の評価は, (1) || (0) となるので, 1 が値となる */

(!(a < b))&&(!(c < d)) ;

/* この式の評価は, (0) && (0) となるので, 0 が値となる */

ここで,注意しなくてはならないのは,3,4番めの式である. 次の例を見よう.

Example 6.8.14 論理AND, OR演算子の変な例は以下の通りである.

int a=0,b=1,c=2,d=3 ; a && (c++ < d) ;

はじめの式はa = 0であるので,&&は右の式の評価は行なわない. したがって,この式の後にcの値を 求めると,c = 2のままである.

int a=0,b=1,c=2,d=3 ; b && (c++ < d) ;

この場合はc++が実行されるが,その順序は,先にインクリメントが実行されるので,b = 1,(c++ < d)

= 0となり,結果は0となる.

int a=0,b=1,c=2,d=3 ; b || (c++ > d) ;

この場合は,b = 1 であるので,||は右の式の評価は行なわない.

このようにCでは, 評価が全て行なわれる前に式の値が決定することがあり,その時点で式の評価が終了 するので,注意しなくてはならない.

6.8.5.7 3項演算子

3項演算子? :は条件演算子とも呼ばれる演算子である.

式1 ? 式2 : 式3

まず, すべての副作用を含めて, 式1が評価され,それが0 でなければ結果は式2の値となり, そうでな ければ式3の値となる. この時評価されるのは式2または式3のいずれかである. 式2,式3の被演算数が 算術型であれば,通常の算術変換が行われて,それらは共通の型となり,それが結果の型となる.

Id: C3.tex,v 1.17 2001-03-20 15:15:41+09 naito Exp

3項演算子は if-else文で書くと長くなる場合に, それを短く表現する手法として使われるが,すべて の条件分岐を表現できるわけではないことに注意.

Example 6.8.15 2つの変数の小さくない方の値を代入する.

max = (a > b) ? a : b ;

Example 6.8.16 例えば, 次のif-else文を考えよう.

if (n == 1) printf("The list has %d item%s\n", n, "") ; else printf("The list has %d item%s\n", n, "s") ;

これは, n が1 であれば item と出力し,n が 2 以上であればitems と出力する. これは, 3項演算子を 使って,出力を1行にまとめることが出来る.

printf("The list has %d item%s\n", n, n==1 ? "" : "s") ;

しかし,3項演算子を余りに多用すると,かえって分かりにくいプログラムになる.

6.8.5.8 コンマ演算子

式1 , 式2

コンマ ,で区切られた式は左から右に評価され,式1の値は捨てられる. 結果の型と値は式2の型と値 である. 式1の被演算数の評価に伴うすべての副作用は,式2の評価を始める前に完了している.

これまでに出てきた int a, b ;

はコンマ演算子を使う例である.

6.8.5.9 演算子の優先順位と結合規則

これら演算子の優先順位,結合規則は,次の通りである. 上ほど優先順位が高い.

演算子 結合規則

( ) [ ] -> . 左から右

! ~ ++ -- + - * & (type) sizeof 右から左

* / % 左から右

+ - 左から右

<< >> 左から右

< <= > >= 左から右

== != 左から右

& 左から右

^ 左から右

| 左から右

&& 左から右

|| 左から右

?: 右から左

= += -= *= /+ %= &= ^= |= <<= >>= 右から左

, 左から右

Id: C3.tex,v 1.17 2001-03-20 15:15:41+09 naito Exp

ただし,単項の+ - * は二項のそれよりも上である.

最も注意しなければならないのは,&,|と==の優先順位である.

Example 6.8.17 int型の変数nが偶数か奇数かを判定するために, if (n & 1 == 0)

としたとしよう. プログラマは, if ((n & 1) == 0)

の意味で書いているかもしれないが,実際には if (n & (1 == 0))

と解釈される.

Example 6.8.18 括弧は不要な場合であっても,括弧を書くことにより,意味が明快になる場合がある. こ の例は,int型の変数year で示される西暦の年号が,うるう年かどうかを判定する.

leap_year = year % 4 == 0 && year % 100 != 0 || year % 400 != 0 ;

うるう年はyearが 4で割りきれ, 100で割りきれないとき,または, 400 で割りきれないときであるが, leap_year = ((year%4 == 0) && (year%100 != 0)) || (year%400 != 0) ;

と書いた方が意味が明快になる.

結合規則は聞きなれない言葉であるが, 以下のような意味を持つ.

Example 6.8.19 =の結合規則は「右から左」であるので, a = b = c ;

は, b = cの代入が行われ,その結果の値(この場合は代入されたbの値)がaに代入される. すなわち,

a = (b = c)という意味となる. ただし,(a = b) = cは(a=b)が左辺値とならないので,文法エラーと なる.

Example 6.8.20 ==の結合規則は「左から右」であるので, a == b == c ;

は,比較 a == bが行われ,その結果の値とcとの比較が行われる. すなわち,(a == b) == cという意味 となる. この場合は,(a == b) == cは文法上正しい構文である.

Example 6.8.21 式 a < b < cの意味は数学的な不等式ではなく,もし, bが aよりも大きければ,1と cを比較し, そうでなければ,0とcを比較していることに注意.

Example 6.8.22 数学的にはb= 0の時,ab/b=aが成り立つが, a/b*b ;

a*b/b ;

は異なった演算規則が適用される. 乗法演算子の結合規則は「左から右」であるので, a/b*bは(a/b)*b, a*b/b は (a*b)/bと計算される. すなわち, a/b*b は a/bの値を評価し, その結果と bの値との積を求 める.

したがって,a,bがともに整数型の時,a/bは商を計算するため,a/b*bはaと等しくなるとは限らない.

また,a,bがともに整数型で,非負であれば,式 a*bの値がその型の範囲内に収まるときa*b/b はaと等 しくなるが,a*bの値がその型の範囲内に収まる保証はない. そのような場合,a*b/bはaと等しくなる保 証はない.

Id: C3.tex,v 1.17 2001-03-20 15:15:41+09 naito Exp

ドキュメント内 note.dvi (ページ 74-81)