2.9 式と演算
2.9.1 算術式
コンピュータは人間のように自由に言葉を話したり、帰納的に物事を推論することは得意ではありません(もっとも、これは本書執筆時点の2002年頃の話であり、変化の激しいこの分野において常にこの常識が通用するとはかぎりませんが…)。そのかわり、人間に比べコンピュータは計算が得意です。人間に計算させれば数年かかるような難解な計算も、現代のコンピュータは一瞬で答えをはじき出します。そろばんチャンピオンでも、暗算チャンピオンでも、コンピュータの計算速度にかなう者はいません。コンピュータが行うその計算はプログラムの賜物なのです。コンピュータは計算が得意なのだから、これをプログラミングで利用しない手はありません。 というよりも、計算なしにプログラムは成り立ちません。
計算のためには、まず式を作ります。ここで言う式とは、基本的に数学で使われる「式」と同じ意味です。式はオペランドと演算子で構成されているものです。
オペランド 演算子 オペランド...
オペランドはやりましたね。では「演算子」とはなんでしょう。演算子はズバリ!「足す」や「引く」で使われる数学記号 + , - などのことです。これらは、以前代入演算を行ったのですでに理解しているでしょう。A = B という式は、A と B というオペランドと、= という演算子で構成されています。注意してほしいのですがコンピュータでは主に ÷ 記号と × 記号を計算に使いません。除算(÷)には / 記号を乗算(×)には * 記号を使います。 C 言語で使われる算術式を表1に示します。
演算子 | 意味 |
---|---|
+ | 加算 |
- | 減算 |
* | 乗算 |
/ | 除算 |
% | 剰余(余り) |
= | 代入 |
演算子が必要とするオペランドの数を項と呼び、これらの演算子は左右に合わせて二つのオペランドが必要になるため、2項演算子と呼ばれます。このほかにもオペランドを一つだけ要求する単項演算子や3つのオペランドを必要とする3項演算子というものも存在します。
C 言語における式は必ず結果の受け取り先が必要です。それは代入演算子を用いて変数が受け取ってもかまいませんし、関数の引数などで渡してもかまいません。とにかく、結果を何かが受け取らなければ、計算する意味がないということです。
#include <stdio.h> int main() { int op1 = 0 , op2 = 0; printf("二つの数値を入力してください>"); scanf("%d %d" , &op1 , &op2); printf("%d + %d = %d\n" , op1 , op2 , op1 + op2); return 0; }
コード1は二つの数値型変数 op1 と op2 に scanf() 関数を用いて値を入力します。その後 printf() 関数を用いて二つの値を加算した結果を出力します。例えば 8 と 16 という値を入力すれば、その結果は 8 + 16 = 24 と出力されることでしょう。
演算結果は代入式を用いて他の変数に格納することもできます。演算結果を画面に表示するだけならばコード1のように、引数として関数に渡す直前に評価する方法でかまいませんが、演算結果を保存する必要がある場合は代入式を用います。
#include <stdio.h> int main() { int op1 = 0 , op2 = 0 , op3; printf("二つの数値を入力してください>"); scanf("%d %d" , &op1 , &op2); op3 = op1 * op2; printf("%d * %d = %d\n" , op1 , op2 , op3); return 0; }
コード2では、変数 op3 に op1 と op2 の乗算した結果を代入しています。演算結果を何度もプログラムで使用する場合、そのたびに計算を繰り返すのは CPU の無駄遣いになるため、計算結果を保存して利用するとよいでしょう。因みに、計算する変数と代入する変数で同じものが使われても問題はありません。例えば op1 = op1 * op2 としても、最初に op1 * op2 が評価されるため、結果に問題はありません。
演算子の優先順位は数学の計算順序と同じです。単純に左から右に計算されるわけではないので注意してください。例えば 2 + 2 * 3 という計算式があった場合、先に 2 * 3 が評価され、その後に 2 + 6 が求められます。計算順序を変更するには、数学同様に括弧に入れます。この場合、加算を先に行いたいのであれば (2 + 2) * 3 とすることで、先に 2 + 2 が評価され、その後に 4 * 3 が求められます。
#include <stdio.h> int main() { printf("2 + 2 * 3 = %d\n" , 2 + 2 * 3); printf("(2 + 2) * 3 = %d\n" , (2 + 2) * 3); return 0; }
コード3の実行実行結果を見ると、括弧を用いない場合は加算よりも乗算が先に評価されています。括弧で加算部分をくくることによって、先に 2 + 2 が評価されていることが確認できます。
また、これまでのプログラムは変数同士を計算していましたが、変数と定数、または定数同士でも、型に互換性があれば問題ありません。定数同士を計算させた場合は、コンパイラがコンパイル時に演算結果を想定できるため、機械語に変換された時には最適化されます。
2.9.2 複合代入演算
通常の代入演算子 = のことを、しばしば単純代入と呼びます。これ以外に、計算と代入を同時に行う演算子が存在します。例えば op1 = op1 + op2 という計算を行いたい場合、これを op1 += op2 と記述することができるのです。このような、他の演算子と単純代入を組み合わせたような代入演算子のことを複合代入と呼びます。算術演算と代入を同時に行う代入演算子を表2に示します。
演算子 | 意味 |
---|---|
+= | 加算代入 |
-= | 減算代入 |
*= | 乗算代入 |
/= | 除算代入 |
%= | 剰余代入 |
#include <stdio.h> int main() { int op1 = 0 , op2 = 0; printf("三角形の底辺と高さを入力してください>"); scanf("%d %d" , &op1 , &op2); op1 *= op2 / 2; printf("三角形の面積 = %d" , op1); return 0; }
コード4の式 op1 *= op2 / 2 に注目してください。これは op1 = op1 * (op2 / 2) という計算に等しいと考えられます。
2.9.3 インクリメント
今の段階では想像し難いかもしれませんが、多くのプログラムでは、現在の変数に対し 1 を加算又は減算するということを頻繁に行います。このとき、通常ならば var = var + 1 又は var += 1 と記述することを思いつくでしょう。もちろんこれでも間違いではありませんが、C言語ではこのような書き方は通常しません。
C言語には、現在その変数が保有する値に「1加算する」「1減算する」専門の演算子が存在します。それがインクリメント演算子とデクリメント演算子です。
変数名++
変数名--
++ がインクリメント演算子です。その変数が現在格納している値に1加算します。-- はデクリメント演算子です。インクリメントとは逆で、その変数が現在格納している値を1減算します。すなわち var = var + 1 は var++ と、var = var - 1 は var-- と記述することができます。
#include <stdio.h> int main() { int iVar1 = 0 , iVar2 = 0; iVar1++; printf("インクリメント後のvar1 = %d\n" , iVar1); iVar2--; printf("デクリメント後のvar2 = %d\n" , iVar2); return 0; }
このプログラムは、0 で初期化した変数 iVar1 と iVar2 を、それぞれインクリメント、デクリメントした後で画面に表示させるというものです。予想通り、インクリメントをすれば1加算され、デクリメントをすれば1減算された結果が表示されます。
インクリメント演算子とデクリメント演算子は 2 種類存在します。コード5は、このうち後置演算子というものを使っているのですが、このほかに前置演算子というものも存在します。それぞれ、後置インクリメント演算子、前置デクリメント演算子、前置インクリメント演算子、前置デクリメント演算子というような呼び方をします。
例えば、前置インクリメント演算子は ++var というように変数の前に演算子を置きます。後置と何が違うかというと、式の結果が違うのです。
++変数名
--変数名
後置演算子は、変数を評価してから 1 加算します。これに対して、前置の場合は最初に 1 を加算してから変数を評価します。コード5のように、インクリメントやデクリメントだけの文であれば、前置でも後置でも結果は同じですが、複雑な多項式内でインクリメント演算子やデクリメント演算子を用いた場合に影響が現れます。
#include <stdio.h> int main() { int iVar1 = 0 , iVar2 = 0; printf("後置インクリメント演算子 = %d\n" , iVar1++); printf("前置インクリメント演算子 = %d\n" , ++iVar2); printf("iVar1 = %d iVar2 = %d\n\n" , iVar1 , iVar2); printf("後置デクリメント演算子 = %d\n" , iVar1--); printf("前置デクリメント演算子 = %d\n" , --iVar2); printf("iVar1 = %d iVar2 = %d\n\n" , iVar1 , iVar2); return 0; }
このプログラムは、引数に値を渡す前にインクリメント及びデクリメントを行っています。このとき、前置と後置でどのように動作が異なるかを確認してみましょう。
実行結果を見ると、前置の場合はインクリメントやデクリメントを行った後に関数に値を渡していますが、後置の場合は画面に値が表示されたあとに評価されていることがわかります。最終的な結果は同じですが、このように関数の引数や多項式でインクリメントやデクリメントを指定する場合は極めて重要な問題です。
つまり、後置演算子の場合は、printf() 関数に現在の変数の値を渡してから変数をインクリメントしているのです。そのため、最初のインクリメントは変数の値 0 を渡してからインクリメントしています。最初の printf() が表示した値は 0 ですが、その後に iVar1 の値はインクリメントされているため 1 となっています。
前置の場合は、インクリメント/デクリメントしてから値を関数に渡しているので、printf() 関数が受け取る値はすでに計算された後です。
このようなことから、もし x = var++ というような計算をする場合はこれに注意する必要があります。x に渡される値は var の値であり、その後に var がインクリメントされるのです。var をインクリメントした上で x に var の値を渡したい場合は x = ++var と記述しなければなりません。