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

先頭へ戻る

この章の概要

この章の概要です。

論理値

プログラミングの世界では、「条件を満たす」「条件を満たさない」のような2択を、 論理値(ブール値)という値で表現することがあります。

10 でも 1000 でも -300 にでもなり得る、普通の整数と違って、 論理値という値は、真(true) 偽(false) のいずれか一方にしかなり得ません。 「条件を満たす」ことを真、「条件を満たさないこと」を偽で表現します。

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

論理値は値なので、変数に入れておくことができます。 それは更に言えば、型があるということでもあります。
C95規格までは、論理値を入れておく変数の型は int型を選択します。 そして、0 という整数は偽、0以外のあらゆる整数は真である、というルールにしています。 真の方は、扱いがやや不鮮明な感じがしますが、「0以外全て真とみなす」というのが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 をインクルードすると、truefalse というマクロ(第24章)がそれぞれ使用できるようになります。これらの名前は、C++ の論理値に関するキーワードと一致しています(Modern C++編【言語解説】第3章)。

論理値と条件式

論理値は、if文の条件式のところで直接使うことができます。

#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
真

コメントに説明を書いてありますが、入力された値に応じて真か偽かを判断して分岐させています。

if文の条件式が「if (boolean)」のように、関係演算子や等価演算子が登場しない形で記述されています。 これは、「変数boolean の値が真であるかどうか」という判定を行っています。

これまでのような、「if( num < 100 )」のような条件式も、実は論理値に変換された上で判定が行われています。 つまり、「変数num の値が 100未満かどうか」という条件をチェックして、真か偽の値に変換しています。 もし、条件が満たされていたら 1 に、満たされていなければ 0 になります

「0以外はすべて真」というルールがありますが、条件式から得られる論理値は必ず 1 になります。 普段、論理値を使うときには、このような想定をせず、 常に「真とは 0以外の値である」と考えていた方が無難です。 つまり、以下のようなコードを書かないようにするべきです。

/* 変数boolean には論理値が入っている */
if (boolean == 1) {  /* 不適切 */
}

変数boolean に、2 とか -1 とかが入っていても、論理値のルールからいって、それは「真」であるべきです。 しかし、「if (boolean == 1)」のような条件式を書いてしまうと、こういった数のときに、条件を満たさなくなってしまいます。 こういう書き方はせずに、「if (boolean)」と書くのが適切です。

逆に、偽であるかどうかを判定する場合は、偽は必ず 0 なので、「if (boolean == 0)」でも大丈夫ですが、 次の項で登場する否定演算子を使って、「if (!boolean)」と書く方法があります。

論理演算子

論理演算子を使うと、複数の条件を組み合わせたり、否定形を作ったりできます。 論理演算子には、以下の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つが両方とも満たされないと真になりません。 結局「num の値が min の値以上、max の値未満かどうか」を判定していることになります。
このような、ある数が、ある範囲内に収まっているかどうかを調べるときなどに、&&演算子が必要になります。

||演算子は、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関数は、引数の値が奇数ならば 1(つまり真)を返すようになっているので、 !演算子で反転させれば、奇数以外かどうかを判定できます。

このように、!演算子は条件式の先頭部分に書きます。 間に空白文字を入れることは許可されています。! は見落としやすいので、空白を入れることを好む人も多いです。

if( ! isOdd( num ) ){
}

!演算子が入ってくると途端にややこしく感じるかも知れませんが、その感覚は正しいです。 人間は否定形をイメージしにくいと言われています(先に肯定形を思い浮かべてから、それを否定するという2段階の思考が必要だそうです)。 ですから、否定形はできるだけ避けるべきです。 例えば、偶数かどうかを判定して返す関数を作っておけば、そちらを呼び出すことで、否定形の if文を避けられます。

優先順位

論理演算子にも優先順位が存在します。 !演算子が一番強く、次に &&演算子、一番弱いのが ||演算子です。 (すべての演算子の優先順位の一覧は、こちらを参照して下さい)。

!演算子では、( ) を使って、優先順位を明確に指定しなければうまくいかないかも知れません。 前の項のサンプルプログラムで登場した if文を、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

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

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

次のプログラムは &&演算子と ||演算子の優先順位を確認しています。

#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関数が実行されることになります。

短絡評価

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

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

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

もし、func関数が非常に時間が掛かる処理であれば、以下のように、関数呼び出しを &&演算子の右側に持ってくれば、 変数num が 0 でない場合に限っては、func関数の呼び出しを避けることができます。

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

しかし、もし func関数を必ず呼び出さなければならないのだとしたら、これは重大なバグです。 変数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;
}

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

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


解答ページはこちら

参考リンク

更新履歴

'2018/2/6 全面的に文章を見直し、修正を行った。

'2018/2/5 「==」や「!=」を関係演算子と表記していたのを、等価演算子に改めた。

'2018/1/31 画面という表現を、標準出力に改めた。

'2018/1/5 コンパイラの対応状況について、対応している場合は明記しない方針にした。

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

▼更に古い更新履歴を展開


このエントリーをはてなブックマークに追加
rss1.0 取得ボタン RSS

前の章へ(第12章 多方向へ分岐させる)

次の章へ(第14章 同じ処理を繰り返す(while文))

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

Programming Place Plus のトップページへ