C言語編 第13章 複数の条件で分岐させる

先頭へ戻る

このエントリーをはてなブックマークに追加

この章の概要

この章の概要です。

論理値

これまでの分岐処理の説明では、「条件を満たすとき」「条件を満たさないとき」という言葉を使いました。 ここではもう少し掘り下げて考えてみます。

プログラミングの世界では、「条件を満たす」「条件を満たさない」のような2択を、 論理値(真偽値)という言葉で表現することがあります。 論理値は、真(true) 偽(false) という2つの値のいずれか一方を持つような値です。

例えば、「変数num の値が 100以上かどうか」という条件式があるとき、 実際に変数num に格納されている値が 200 であれば、「この条件式の結果は真である」と言えます。 同様に、変数num の値が 50 であれば、「この条件式の結果は偽である」と言います。

論理値は値なので、何らかのデータを使って表現できそうです。 C言語の場合、0 という整数を偽とし、0以外のあらゆる整数は真であると考えます。 「真」の扱いがやや不鮮明な感じがしますが、「0以外全て真」というのがC言語的な答えです。

このように、C言語の論理値はただの整数値なので、通常、int型で表現します。 あるいは、第26章で説明する方法を使って、論理値を表す専用の型を作ります。

C99 (論理値型)

C99 では、論理値を表す専用の型として、_Bool型が追加されています。 この型を使った方が、int型を使用するよりも意味を明確にできます。

論理値を int型で表現する場合と同様に、_Bool型でも 0 なら偽、0以外なら真になります。

#include <stdio.h>

int main(void)
{
	_Bool a = 0;
	_Bool b = 1;
	
	if( a ){
		puts( "a は真" );
	}
	if( b ){
		puts( "b は真" );
	}
	
	return 0;
}

実行結果:

b は真

stdbool.h をインクルードすると、 bool(⇒リファレンス)、 true(⇒リファレンス)、 false(⇒リファレンス) というマクロ(第24章)がそれぞれ使用できるようになります。 これらの名前は、C++ の論理値に関するキーワードと一致しています(C++編【言語解説】第8章)。

なお、これら論理型に関する機能を、 VisualC++、Xcode のいずれもサポートしています。


最後に、真が 0以外、偽が 0 であることを確認するプログラムを見ておきましょう。

#include <stdio.h>

int main(void)
{
	int boolean;
	char str[20];

	puts( "適当な整数を入力して下さい。" );
	fgets( str, sizeof(str), stdin );
	sscanf( str, "%d", &boolean );


	if( boolean ){
		/* 真とは条件を満たすことを言う */
		/* 真は 0以外なので、入力された値が 0以外のときにここに来る */
		puts( "真" );
	}
	else{
		/* 偽とは条件を満たさないことを言う */
		/* 偽は 0なので、入力された値が 0 のときにここに来る */
		puts( "偽" );
	}

	return 0;
}

実行結果:

適当な整数を入力して下さい。
8
真

コメントに説明を書いてありますが、入力された値に応じて真か偽かを判断して分岐させています。 この例のように、条件式に「==」や「!=」などの関係演算子が登場しないこともあり得ます。 どちらへ分岐するのかは、値が 0 なのか、0以外なのかを元に判断します。 ちなみに、負数は 0以外なので、必ず真であると判定されます。

では if( num < 100 ) のような条件式はどういう扱いを受けるのでしょう。 この場合、変数num の値が 100未満であれば、その結果が 1 として扱われます。 従って、if( 1 ) という形であると考えられ、結果は真です。 変数num の値が 100未満でなければ、その結果は 0 と扱われ、if( 0 ) という形であると考えられ、その結果は偽です。 このように、条件式の結果に関しては明確に、真は 1 、偽は 0 です

分かりづらいですが、真が 1 であると定まっているのは条件式の結果だけです。 普段は、そのようなことは気にせず、常に「真とは 0以外の値である」と考えていた方が無難です

論理演算子

論理演算子という新たな演算子を使うと、複数の条件を組み合わせたり、否定形を作ったりできます。 論理演算子には、以下の3種類があります。

論理演算子 名前 意味
&& 論理積演算子(AND演算子) 〜かつ〜
|| 論理和演算子(OR演算子) 〜または〜あるいはその両方
! 否定演算子(NOT演算子) 〜ではない

例えば、次のように使います。

#include <stdio.h>

int main(void)
{
	int min = 100;	/* 下限値 */
	int max = 200;	/* 上限値 */
	int num;
	char str[20];

	puts( "適当な整数を入力して下さい。" );
	fgets( str, sizeof(str), stdin );
	sscanf( str, "%d", &num );


	if( min <= num && num < max ){
		printf( "%d は %d以上 %d未満\n", num, min, max );
	}
	else{
		printf( "%d は %d未満 あるいは %d以上\n", num, min, max );
	}

	return 0;
}

実行結果:

適当な整数を入力して下さい。
150
150 は 100以上 200未満

&&演算子は、2つの条件式を組み合わせ、両方の条件が満たされたときにだけ、条件式全体が真と見なします。 この例の場合なら、「num が min以上」と「num が max未満」という2つの条件式を組み合わせており、 この2つが両方とも満たされないと真になりません。

||演算子についても使い方は同様です。 ||演算子は、2つの条件式を組み合わせ、片方だけあるいは、両方とも満たされたときにだけ、条件式全体が真になります。 ||演算子の説明が、「または」とだけ書かれていることが多いですが、両方とも満たされた場合も含まれることに注意して下さい。 (例えば、「ミカンまたはリンゴを下さい」ではどちらか片方だけしか欲していないように受け取れます。)

!演算子は、使い方が異なります。 例えば、ある整数が奇数かどうかを判定する関数を自作したとします。 !演算子を使った否定形の判定を使えば、その自作関数を偶数の判定に利用できます。

#include <stdio.h>

int isOdd(int num);


int main(void)
{
	int num;
	char str[20];

	puts( "適当な整数を入力して下さい。" );
	fgets( str, sizeof(str), stdin );
	sscanf( str, "%d", &num );


	if( !isOdd( num ) ){
		printf( "%d は奇数ではありません。\n", num );
	}

	return 0;
}

/*
	奇数かどうか判定する。
	引数
		num:	判定する値。
	戻り値
		奇数なら 0以外、奇数でなければ 0 を返す。
*/
int isOdd(int num)
{
	if( num % 2 == 1 ){
		return 1;
	}
	return 0;
}

実行結果:

適当な整数を入力して下さい。
4
0 は奇数ではありません。

このように !演算子は、条件式の先頭部分に書きます。 複数の条件式をつなげる演算子ではないので、このように && や || とは異なった記法になります。 !演算子は、条件式の結果を否定します。すなわち、真であれば偽に、偽であれば真、というように結果を反転させます

今回のサンプルの場合、isOdd関数は、実引数が奇数ならば 0以外(つまり真)を返すようになっているので、 !演算子で反転させれば、奇数以外かどうかを判定できます。

!演算子は、場合によっては ( ) を使って、優先順位を明確に指定しなければうまくいかないかも知れません。 先ほどの例を isOdd関数を使わず、直接 if文の中に判定を書いたとしましょう。 次のようにしてしまうと、同じ結果になりません。

#include <stdio.h>

int main(void)
{
	int num;
	char str[20];

	puts( "適当な整数を入力して下さい。" );
	fgets( str, sizeof(str), stdin );
	sscanf( str, "%d", &num );


	if( !num % 2 == 1 ){
		printf( "%d は奇数ではありません。\n", num );
	}

	return 0;
}

実行結果:

適当な整数を入力して下さい。
4

!演算子の優先順位は強いので、上のように書くと、変数num を否定してから、2 の剰余を求めることになってしまいます。 正しくは、変数num の 2 の剰余を求め、これが 1 かどうか比較した結果を反転しなければなりませんから、 条件式全体を ( ) で囲み、その全体を !演算子で否定しなければなりません。

if( !(num % 2 == 1) ){
	printf( "%d は奇数ではありません。\n", num );
}

これが正しいです。

( ) を使わない間違った例の場合、変数num がどんな値のときに、printf関数が実行されるか考えてみて下さい。 これは結構高度な問題です。

どうでしょう。!演算子が入ってくると途端にややこしく感じなかったでしょうか? その感覚は正しいです。 人間は否定形をイメージしにくいと言われています(先に肯定形を思い浮かべてから、それを否定するという2段階の思考が必要だそうです)。 ですから、否定形は基本的に避けるべきです。 先ほどの例なら、素直に偶数かどうか判定する条件式を書くのが正常なコーディングでしょう。

優先順位

論理演算子にも優先順位が存在します。 !演算子が一番強く、次に &&演算子、一番弱いのが ||演算子です

例えば、次の例は &&演算子と ||演算子の優先順位を確認しています。

#include <stdio.h>

int main(void)
{
	int num;
	char str[20];

	puts( "適当な整数を入力して下さい。" );
	fgets( str, sizeof(str), stdin );
	sscanf( str, "%d", &num );


	if( (num == 0) || (num % 2 == 1) && (num >= 100) ){
		printf( "%d は 0 または、奇数かつ 100以上。\n", num );
	}

	return 0;
}

実行結果:

適当な整数を入力して下さい。
103
103 は 0 または、奇数かつ 100以上。

||演算子よりも &&演算子の方が優先されるので、奇数判定と 100以上の判定とが先に結び付きます。 従って、「奇数であること」と「100以上であること」は両方同時に満たす必要があります。 この2つの結果を@と呼ぶとします。

続いて、0 かどうかの判定と@とが ||演算子で結び付きます。 従って、「0 である」または「@」あるいはその両方が真になったとき、条件式全体が真になり、 printf関数が実行されることになります。

短絡評価

&&演算子や ||演算子を使う場合、短絡評価(ショートサーキット)という性質を知っておくべきです。 これは、複数の条件式を組み合わせている場合、&& や || の左側にある条件式を先に調べ、 その段階で、全体の結果が確定するのであれば、右側の条件式を調べないというものです

一度落ち着いて、上の2つの文章を理解して下さい。 いくら考えても意味が分からないようなら、この章の内容のどこかの段階から理解できていない可能性があります。

さて、短絡評価は余計な判定処理を極力行わないようにしてくれるため、うまくすれば実行効率を向上できます。 例えば、関数を呼び出している場合、その関数の内部の処理が非常に複雑で時間がかかる可能性があります。

if( func() && num == 0 ){
}

もし、func関数が非常に時間の掛かる処理であれば、

if( num == 0 && func() ){
}

このように、関数呼び出しを &&演算子の右側に持ってくれば、変数num が 0 でない場合に限って、 func関数の呼び出しを避けることができます。

しかし、もし func関数を必ず呼び出さなければならないのだとしたら、これは重大なバグです。 変数num が 0でない場合に限ってだけ、func関数が呼び出されないことになり、短絡評価というものを理解していなければ、 このバグの原因はさっぱり分からないことでしょう。

この問題を避けるには、可能であれば、条件式の中で関数を呼び出すのを避けることです。 1つ目の手段としては、

result = func();  /* 先に呼び出して結果を受け取っておく */
if( num == 0 && result ){
}

この場合、func関数の処理時間の長さを避けるために、短絡評価を利用しようとしているのなら、台無しになってしまいます。 もう1つの手段は、

if( num == 0 ){
	if( func() ){
	}
}

このように if文を2つに分けてしまうことです。 この場合だと、短絡評価を利用するのと同じ結果になり、変数num が 0 のときにだけ func関数を呼び出すことになります。

しかし、&&演算子で条件式を連結する記述は、ごく一般的なものです。 重要なのは、短絡評価の挙動を正しく理解しておくことです。


練習問題

問題@ 次の2つの if文を、1つの if文に書き直してください。

#include <stdio.h>

int main(void)
{
	int num;
	char str[20];
	
	puts( "整数を入力して下さい。" );
	fgets( str, sizeof(str), stdin );
	sscanf( str, "%d", &num );

	if( num > 100 ){
		if( num < 300 ){
			puts( "OK!" );
		}
	}

	return 0;
}

問題A 2つの文字列が一致しているかどうかを、strcmp関数と論理演算子を使って判定する if文を書いて下さい。

問題B 標準入力から受け取った整数が、次の条件を全て満たすときに画面に「OK!」と表示するプログラムを作って下さい。


解答ページはこちら

参考リンク

更新履歴

'2017/7/30 clang 3.7 (Xcode 7.3) を、Xcode 8.3.3 に置き換え。

'2017/3/25 VisualC++ 2017 に対応。

'2016/10/15 clang の対応バージョンを 3.7 に更新。

'2015/10/12 clang の対応バージョンを 3.4 に更新。

'2015/9/5 VisualC++ 2012 の対応終了。

'2015/8/18 VisualC++ 2010 の対応終了。

'2015/8/15 VisualC++ 2015 に対応。

'2014/10/18 clang 3.2 に対応。

'2014/2/1 VisualC++ 2013 に対応。

'2014/1/14 VisualC++ 2008 の対応終了。

'2014/1/13 「C99 (論理値型)」の項を追加。

'2009/7/12 「論理演算子」のサンプルプログラムの間違いを修正( printf関数の引数が不足していた)

'2009/3/16 新規作成。



前の章へ

次の章へ

C言語編のトップページへ

Programming Place Plus のトップページへ