先頭へ戻る

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

Programming Place Plus トップページ -- C言語編

先頭へ戻る

この章の概要

この章の概要です。


論理値

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

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

たとえば、「変数num の値が 100以上かどうか」という if文があるとき、「この条件式の結果は真である」と言えます。変数num の値が 50 であれば、「この条件式の結果は偽である」といいます。

C言語の論理値は、偽を 0 で、真を 0以外の値で表現するというルールにしています。つまり、ソースコードでの表現は、結局のところ整数値なのですが、0 なのかそうでないのかだけに注目するように考えるわけです。

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

#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 1;

    if( a ){
        puts( "a は真" );
    }
    if( b ){
        puts( "b は真" );
    }

    return 0;
}

実行結果:

b は真

変数a の値は 0 なので偽です。変数b の値は 1 なので真です。


論理型

論理値を表すための型を論理型と呼び、C言語には _Bool型があります。

論理値は、0 か 0以外か、つまり整数で表現できるので、int型として扱うことも可能です。実際、古いC言語プログラムではそうしています。しかし、int型は普通の整数のつもりで使うことが圧倒的に多いので、論理値であることを明確にする意味で、専用の _Bool型を使ったほうがいいです。

#include <stdio.h>

int main(void)
{
    _Bool a = 0;
    _Bool b = 1;

    if( a ){
        puts( "a は真" );
    }
    if( b ){
        puts( "b は真" );
    }

    return 0;
}

実行結果:

b は真

型は _Bool になっても、結局、0 や 1 を直接書いています。論理値であることを明確に示したいという意図を考えると、これは中途半端な対応です。

論理値の 0 と 1 にも明確な名前をつける方法があります。

#include を使って、stdbool.h を取り込むと、booltruefalse という名前が使えるようになります。

bool は _Bool の代わり、true は 1 の代わり、false は 0 の代わりに使える名前です。「代わり」なだけであって、ソースコード上での見た目以外にはなんら違いはありません。

【上級】bool、true、false はマクロ(第23章)です

#include <stdio.h>
#include <stdbool.h>

int main(void)
{
    bool a = false;
    bool b = true;

    if( a ){
        puts( "a は真" );
    }
    if( b ){
        puts( "b は真" );
    }

    return 0;
}

実行結果:

b は真

論理値と条件式

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

論理値を判定するときには「0以外はすべて真」というルールがありますが、条件式から得られる論理値は必ず 0 か 1 のどちらかになります。そのため、以下のようなコードを書かないようにするべきです。

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

もし、変数boolean に 2 や -1 が入っていたとしても、論理値のルールでは「真」です。しかし、「if( boolean == 1 )」のような条件式を書いてしまうと、2 や -1 とは一致しませんから「偽」であると判定されてしまいます。そのため、「if( boolean )」のような書き方をするべきです。

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

一方で、偽であるかどうかを判定する場合、偽は必ず 0 なので「if( boolean == 0 )」でも問題ありません。しかし、次の項で登場する論理否定演算子を使って、「if( !boolean )」と書くことが多いです。

// 変数boolean には論理値が入っている
if( boolean == 0 ){      // 問題ないが、普通の整数の比較にみえる
}
if( boolean == false ){  // 問題ないが、無駄
}
if( !boolean ){          // 適切
}

論理演算子

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

論理演算子 名前 意味
&& 論理積演算子(論理AND演算子) ~かつ~のとき真
|| 論理和演算子(論理OR演算子) ~または~のとき、あるいは両方とも満たすとき真
! 論理否定演算子(NOT演算子) 真と偽を入れ替える

&&演算子は、2つの条件式を組み合わせ、両方の条件が満たされたときにだけ、条件式全体を真とします。それ以外の場合は偽です。

#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未満

このサンプルプログラムでは、「num の値が min の値以上」と「num の値が max の値未満」という2つの条件式を組み合わせており、この2つが両方とも満たされないと真になりません。つまり「num の値が min の値以上、max の値未満かどうか」を判定しています。このように、ある数が、ある範囲内に収まっているかどうかを調べるときなどに、&&演算子が必要です。

||演算子は、2つの条件式を組み合わせ、片方の条件だけ、あるいは両方の条件がともに満たされたときに、条件式全体を真とします。それ以外の場合は偽です

||演算子の説明では、「または」とだけ書かれていることが多いですが、両方とも満たされた場合も含まれることに注意してください。

#include <stdio.h>

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

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

    if( num % 3 == 0 || num % 5 == 0 ){
        printf( "%d は 3 か 5 の倍数\n", num );
    }
    else{
        printf( "%d は 3 の倍数でも 5 の倍数でもない\n", num );
    }

    return 0;
}

実行結果:

適当な整数を入力してください。
150
150 は 3 か 5 の倍数

!演算子は、条件式の結果を否定します。すなわち、真であれば偽に、偽であれば真、というように結果を反転させます。複数の条件を組み合わせるものではないので、&&演算子や ||演算子とは使い方が異なります。

【上級】正確にいえば、!演算子は、オペランドの値が 0 ならば 1 にし、0以外ならば 0 にするという効力を持ちます。結果は必ず int型です。

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

#include <stdio.h>
#include <stdbool.h>

bool 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:    判定する値。
    戻り値
        奇数なら true、奇数でなければ false を返す。
*/
bool isOdd(int num)
{
    if( num % 2 == 1 ){
        return true;
    }
    return false;
}

実行結果:

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

isOdd関数は、引数の値が奇数ならば true を返すようになっているので、!演算子で反転させれば、奇数以外かどうかを判定できます。

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

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!」と出力するプログラムを作ってください。

問題④ 標準入力から受け取った西暦年が、閏年かどうか判定するプログラムを作成してください。
※閏年の定義を正確に知っていますか?「4年に1度」だけでは不正解です。


解答ページはこちら

参考リンク


更新履歴

'2018/6/5 第18章から練習問題⑥を移動してきて、練習問題④とした。

'2018/5/4 「論理演算子」の項に、||演算子のサンプルプログラムを追加。

≪さらに古い更新履歴を展開する≫



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

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

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

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
Twitter でツイート Twitter をフォロー LINE で送る
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー