WisdomSoft - for your serial experiences.

3.1 真偽の判定

2つの値の大小関係を調べる関係演算や、2つの値が等しいかどうかを調べる等価演算を紹介します。

3.1.1 関係演算と等価演算

これまでのプログラムは、常に上から下へと順番に実行される逐次実行でしたが、プログラムは状況に応じて処理を変更させる必要があります。すなわち分岐や繰り返しといった処理です。これらの条件分岐や繰り返し処理は、プログラミングの基本中の基本であり、最も重要な部分でもあるので、本章では入念にその仕組みを解説し、プログラムの流れを自由に制御できるようになるまで導きます。

プログラムは、プログラムの流れを制御するために何らかの情報を題材にする必要があります。例えば、「変数 A が 100 以下であれば」とか「変数 A と 変数 B の値が等しければ」というような比較による結果でプログラムを分岐させることができれば、状況に応じた適切な処理へと導くことができると考えられます。

そこで、C 言語では比較結果が(TRUE)であるか(FALSE)であるかによって状況を判断します。真偽は値によって識別され 0 であれば偽、そうでなければ真であると解釈されます。例えば、変数 A が 変数 B と等しいかどうかを評価した結果、等しい状態を真と呼び、等しくない状態を偽と呼びます。

通常、値の比較は関係演算子等価演算子、及び論理演算子 を用います。これらの演算子は式を評価して、その結果に応じて真、又は偽を返します。真偽の実体は数値なので、これらの演算子が返す結果は int 型です。各演算子は表1にまとめます。

表1
演算子 内容
関係演算子
A < B A が B より小さければ真
A <= B A が B と同じか小さければ真
A > B A が B より大きければ真
A >= B A が B と同じか大きければ真
等価・不等価演算子
A == B A と B が等しければ真
A != B A と B が等しくなければ真
論理演算子
A && B A と B が真ならば
A || B A または B が真ならば
!A A が偽であれば真

「等しければ」という評価を行う場合は A = B ではなく A == B であることに注意してください。A = B と記述した場合は B を A に代入するという意味になってしまうため、誤った結果が得られることになるでしょう。等価演算によるニアミスを防ぐには、左オペランドに定数を持ってくる癖をつけるという方法があります。左オペランドが定数の場合、誤って代入演算子 = を指定してしまっても、定数に値を代入することはできないためコンパイルエラーが発生します。そのため、等価演算を指定するべき場所に代入演算子を指定してしまったミスに気づくことができるでしょう。

ただし、(A = B) == 0 という構文は誤りではありません。代入演算子 = は結果(この場合は最終的な A の値)を返すため、何らかの計算結果を調べたい場合などに使うことは可能です。例えば、何らかの計算を行い、その結果を変数に格納した後、エラーチェックなどを行う場合にこの性質を利用することがあります。B + C の結果を A に保存するが、その結果が 0 であるかどうかを調べるような場合、(A = B + C) == 0 と記述すれば、その真偽を問うことができます。

論理演算子は等価・不等価演算子よりも、等価・不等価演算子は関係演算子よりも優先順位が低いので、これらの演算子を同時に使う場合は意識する必要があります。

コード1
#include <stdio.h>

int main() {
	int iVar1 , iVar2;
	printf("2つ値を入力してください>");
	scanf("%d %d" , &iVar1 , &iVar2);

	printf("iVar1 == iVar2 = %d\n" , iVar1 == iVar2);
	printf("iVar1 < 1000 = %d\n" , iVar1 < 1000);
	printf("iVar1 < iVar2 && iVar1 > 100 = %d\n" , (iVar1 < iVar2) && (iVar1 > 100));

	return 0;
}
実行結果
コード1 実行結果

このプログラムを実行すると、二つの値を入力するように求められます。値は iVar1 と iVar2 変数にそれぞれ格納され、その後、条件式によってそれらの値の関係を調べています。

最初の printf() は、iVar1 と iVar2 が等しいかどうかを調べています。もし、入力した二つの値が同一の値であれば、この結果は真となるので 0 以外の値が出力されるでしょう。通常、真を表す 0 以外の値には 1 が使われます。次の printf() 関数では iVar1 が 1000 よりも低いかどうかを評価した結果を出力し、最後の printf() 関数では、iVar1 が iVar2 よりも低く、かつ、iVar1 が 100 よりも大きいかどうかを評価した結果を出力します。

最後の論理演算子 && を用いた条件式は、少し複雑に見えますが、こうすることによって非常に柔軟な比較ができるようになります。&& 演算子によって、「左右の比較演算の結果が真のときのみ真である」という評価を行えます。

&& や || の特徴として、条件によっては左辺のオペランドを解析した時点で結果がわかることがあります。論理演算子は常に左オペランドから右オペランドの順番で解析するのですが、&& は一方のオペランドが偽であれば、もう一方のオペランドが何であれ結果は偽です。そのため、もし左辺のオペランドが偽であれば、右辺の式が何であれ結果は常に偽であることが保証されるため、プログラムは右オペランドを評価せずに偽を返します。同様に || 演算子の場合は、一方のオペランドが真であれば、その結果は常に真であることが保証されるため、左オペランドが真であれば、右オペランドは評価せずに真を返します。

普段、このことを意識する必要はありませんが、評価時に何らかの演算を行うような仕掛けを施す場合は危険です。例えば、論理演算子の右オペランドでインクリメントやデクリメントを使った場合、左オペランドだけで結果が判定されてしまうと、右オペランドは計算されません。これはコード2で証明することができます。 

コード2
#include <stdio.h>

int main() {
	int iVar = 0 , iTmp;

	iTmp = 0 && iVar++;
	printf("iVar = %d\n" , iVar);
	return 0;
}
実行結果
コード2 実行結果

コード2の iTmp = 0 && iVar++; という行に注目してください。左オペランドに定数 0 を指定していますが、これはすなわち偽です。プログラムは左オペランドを評価した時点で、この式の結果が偽であることを保証できるため、右オペランドを評価する必要はありません。そのため iVar++ は実行されないので、printf は iVar の値を 0 と表示するでしょう。6行目を 1 && iVar++ と書き換えれば iVar++ が評価されることを確認できます。

ところで、「一方が真、もう一方が偽であれば真」という結果を得たい場合はどのようにすればよいでしょうか?この論理演算を排他的論理和といいます。排他的論理和は、C言語の 3 つの論理演算子を用いて計算できます。ビットごとの論理演算と混同しないでください。ここの排他的論理和は条件式の論理値を得るためのものです。

これを実現するには、評価するべき二つのオペランドのうち一方が真であることを確認し、双方が真であれば否定して偽にすればよいのです。一方が真であれば真というのは論理和 bool1 || bool2、双方が真であればというのは論理積 bool1 && bool2 です。最初の論理和と、論理積を否定した結果を論理積で求めます。

(bool1 || bool2) && !(bool1 && bool2)

これを求めれば、bool1 と bool2 のうち、「一方が真、もう一方が偽であれば真」という排他的な結果を得ることができます。

コード3
#include <stdio.h>

int main()
{
	int iBool1 , iBool2;
	printf("二つの論理値を入力してください>");
	scanf("%d %d" , &iBool1 , &iBool2);

	printf("iBool1 XOR iBool2 = %d\n" , (iBool1 || iBool2) && !(iBool1 && iBool2));
	return 0;
}
実行結果
コード3 実行結果

これは、入力された二つの値のうち、一方が真で、もう一方が偽である時のみ、真であるという結果を得るためのテストプログラムです。両方のオペランドが偽、または真であった場合は 0 すなわち偽が表示され、そうでなければ 1 すなわち真という結果が得られるでしょう。