記憶域(変数など)を変更することができる演算子は、
= や += などの複合代入演算子、
そして ++ や -- 演算子だけです。
そしてこれらも演算子なので、例外なく優先順位や結合規則に従って、
演算結果のデータ算出する処理が行われます。
しかし優先順位や結合規則は、演算結果を算出する時の順番に対する規則であって、
記憶内容を変更するタイミングまで規則付けている訳ではありません。
よって、変更する変数を、一つの文の中で複数使う表現は避けなければなりません。
代入演算子 = や 複合代入演算子の優先順位は、下から2番目で、右から左への
結合規則になっています。
そして、左に表現した記憶域の変更値が、演算子としての演算結果になります。
int a , b, c; と宣言してある時、次のような式が書けます。
以下の左右で行う処理は全く同じです。
左側のプログラミングは、C言語の演算子優先順位と結合規則をうまく利用して、
右側の6つの文(セミコロンが6個)を、2つの文にまとめて作った記述です。
どちらも同じなので、どちらが優れていると言うわけではありません。
ですが、左のように書く人がいるので、それを、読めなければなりません。
このような表現を好んで書く人も多いのですが、以下のような表現を使ってはいけません。
printf("%d, %d\n", c = a = 3, c );
この実行が終わった時に、変数aとcは3に変更されていますが、printfの引数に渡す情報が、
3に変更した後の値が渡されることを期待してはいけません。
cが0で実行したときの実行表示は、 3,0 となるか
、 3,3 となるかはコンパイラ依存で、わかりません。
(つまり、変更する変数を、一つの文の中で複数使う表現は避けなければなりません。)
なお、標準入力から、入力した1byteをcに記憶する繰り返しで、次の左側のコードがよく使われます。
(左右で行っている内容は同じで、左の方がシンプルに見えます。)
左側では、c = getchar()で、入力した文字コードを=演算子で変数cに記憶することを先行します。
そしてその演算結果であるcの記憶内容が、エラーを意味するEOFでない間、繰り返しています。
ここで、()をなしにする次の表現は、上記の処理と全く違って正しくありません。
while( c = getchar() != EOF){
この場合は、getchar() != EOFの演算が先行します。
入力コードがエラーでないかの判定結果である1か0をcに記憶し、
その記憶値をwhileの条件の判定値として繰り返します。
これら演算子は、演算子を置く位置(前置と後置)によって、演算結果が異なります。
オペランドの前にある時は、変数の変化後の値が演算結果になり、
後ろにある場合は変数変更前の記憶内容が結果のデータになります。以下に例を示します。
結果的に以下の左右は同じ処理を行っていることになります。
まとめた式 | ばらした式 |
---|---|
int b; char a[5] ="ABC"; char *p; int i = 0; b = ++i; /* i は1増えて1になり、b に1が記憶される */ b = i--; /* b に、i が減る前の1が記憶され、i は1減って0になります */ b = a[i++]; /* b に a[0]の'A' が記憶され、i は1増えて1になります */ b = a[i++]; /* b に a[1]の'B' が記憶され、i は1増えて2になります */ b = a[--i]; /* i は1減って1になり、b に a[1]の'B' が記憶されます */ b = a[i]++; /* b に a[1]の'B' が記憶され、a[1] は1増えて'B'から'C'になります */ b = --a[i]; /* a[1]が 1減って'C'から'B'になり、それが bに記憶されます */ p = a; b = *p++; /* b に pが指し示す*a の 'A'が記憶され、p は、a+1 の記憶内容に変化します */ b = *p++; /* b に pが指し示す*(a+1) の 'B'が記憶され、p は、a+2 の記憶内容に変化します */ b = *--p; /* p は は1減って a+1 になり、b に *(a+1)の'B' が記憶されます */ b = (*p)++; /* b に pが指し示す*(a+1) の'B'が記憶され、*(a+1) は1増えて'B'から'C'になります*/ b = --*p; /* pが指し示す*(a+1)が 1減って'C'から'B'になり、それが bに記憶されます */ i = 1; a[i++] = 'B'; /* a[1] に'B'が記憶され、 iは増える。*/ a[--i] = 'B'; /* iが減って1から0になり、a[0]に'B'が記憶される */ p = a + 1; *p++ = 'B'; /* *(a+1) に'B'が記憶され、 pは a+1から a+2に増える */ *--p = 'B'; /* pは a+2から a+1に減って、pが指し示す*(a+1) に'B'が記憶される */ |
int b; char a[5] ="ABC"; char *p; int i = 0; ++i; b = i; b = i; i--; b = a[i]; i++; b = a[i]; i++; --i; b = a[i]; b = a[i]; a[i]++; --a[i]; b = a[i]; p = a; b = *p; p++; b = *p; p++; --p; b = *p; b = *p; (*p)++; --*p; b = *p; i = 1; a[i] = 'B'; i++; --i; a[i] = 'B'; p = a + 1; *p = 'B'; p++; --p; *p = 'B'; |
なお=の左側の式において、++や--の処理順番が最後になる式は、記憶域を表現していないので、
コンパイルエラーになります。例えば、次のようなコードがエラーです。
この場合、'='の左のオペランドが、左辺値になっていません のようなエラーメッセージをだすコンパイラが多いようです。
この左辺値(: LValue)とは、=の左に記述して、変更が可能な記憶域の表現という意味の単語です。
++i = 1; /* コンパイルエラー */ ++a[i] = 'B'; /* コンパイルエラー */ ++*p = 'B' /* コンパイルエラー */
上記は、 i + 1 = 2; の i + 1 に 2 を 記憶させる式を書いた時のコンパイルエラーと同じ種類です
また、次のように変更する変数iやpを、一つの文の中で複数使う表現は避けなければなりません。
++が付いていない変数の評価が、増える前の値なのか?
増えた後の値なのかが決められていないためです。
(これは、コンパイルエラーになりません。)
a[i+1] = a[i++];/* iが0で実行する場合 a[1] が変更されるか、a[2]が変更されるかわからない */ *(p+1) = *p++; /* pが100000の場合、100001番地の内容を変更するのか、 100002番地の内容を変更するのか分からない */