C言語の基本|条件式の反転

条件の向きを変えれば、コードの見え方も変わる。真と偽を入れ替えて、意図が伝わる分岐を作ろう。

if~else文は、条件式が真ならif側、偽ならelse側を実行する制御文です。この条件式を反対の意味に書き換えると、if側とelse側の役割を入れ替えられます。

たとえば、音量設定が0以上20以下なら正常、それ以外ならエラーと判定する場合、次の2通りで表現できます。

  • 正常な範囲をif側で判定する
  • 範囲外をif側で判定する

どちらも同じ結果にできます。大切なのは、正常と異常のどちらを先に見せると、処理の目的が伝わりやすいかという点です。

条件式を反転する代表的な方法は次の2つです。

  • 比較演算子や論理演算子を反対の形へ書き換える
  • 否定演算子!を使って条件式全体を反転する

図1:条件式を反転してif側とelse側を入れ替える

この図から分かること

正常な範囲をif側へ置く書き方と、範囲外をif側へ置く書き方では、条件式の真と偽が入れ替わります。

ただし、同じ動作を保つには、条件式を反転するだけでなく、ifブロックとelseブロックの処理も入れ替える必要があります。

書き方if側else側
正常な条件を判定正常時の処理異常時の処理
異常な条件を判定異常時の処理正常時の処理

条件式を反転するメリット

条件式を反転すると、重要な処理をif側へ移動できます。

たとえば、入力値を検査するプログラムでは、次のどちらの構成も考えられます。

  • 正常な入力を先に判定する
  • 異常な入力を先に判定する

正常な処理を中心に見せたい場合は、正常条件をif側へ置くと自然です。入力ミスや範囲外の値を強調したい場合は、異常条件をif側へ置くと目的が伝わりやすくなります。

範囲内をif側に置く書き方

最初は、正常な範囲を条件式にします。

音量設定値が0以上20以下なら有効とする場合、条件は次の2つです。

  • volume >= 0
  • volume <= 20

両方を満たす必要があるため、&&で接続します。

音量設定値が有効な範囲かを判定するプログラム

ファイル名:8_5_1.c

#include <stdio.h>

int main(void)
{
    int volume;

    printf("音量設定値を入力してください(0~20)> ");
    scanf("%d", &volume);

    /* 有効な範囲をif側で判定する */
    if (volume >= 0 && volume <= 20) {
        printf("音量設定値は有効です。\n");
    } else {
        printf("音量設定値は範囲外です。\n");
    }

    return 0;
}
実行結果の例
音量設定値を入力してください(0~20)> 12
音量設定値は有効です。
プログラムの詳しい解説

条件式は、volumeが0以上であり、さらに20以下であるかを確認しています。

volume >= 0 && volume <= 20

12を入力すると、12は0以上であり、20以下でもあります。2つの条件がどちらも真になるため、ifブロックが実行されます。

一方、25を入力するとvolume <= 20が偽になるため、条件式全体も偽になり、elseブロックが実行されます。

0と20も有効な値に含めたいので、下限には>=、上限には<=を使用しています。

範囲外をif側に置く書き方

次は、同じ判定を反転し、範囲外の条件をif側へ置きます。

0以上20以下の範囲から外れるのは、次のどちらかの場合です。

  • 0未満である
  • 20より大きい

どちらか一方に該当すれば範囲外なので、||で接続します。

範囲外の音量設定値を先に検出するプログラム

ファイル名:8_5_2.c

#include <stdio.h>

int main(void)
{
    int volume;

    printf("音量設定値を入力してください(0~20)> ");
    scanf("%d", &volume);

    /* 範囲外をif側で判定する */
    if (volume < 0 || volume > 20) {
        printf("音量設定値は範囲外です。\n");
    } else {
        printf("音量設定値は有効です。\n");
    }

    return 0;
}
実行結果の例
音量設定値を入力してください(0~20)> 25
音量設定値は範囲外です。
プログラムの詳しい解説

条件式は、volumeが0未満、または20より大きいかを調べています。

volume < 0 || volume > 20

25を入力するとvolume > 20が真になるため、ifブロックが実行されます。

12を入力した場合は、volume < 0とvolume > 20が両方とも偽になります。||でつないだ条件は、両方が偽のときに全体が偽になるため、elseブロックへ進みます。

8_5_1.cと8_5_2.cでは、条件式の向きとブロックの順番が異なりますが、入力に対する判定結果は同じです。

&&と||が入れ替わる理由

範囲内の条件では、下限と上限の両方を満たす必要があります。

volume >= 0 && volume <= 20

範囲外では、下限より小さいか、上限より大きいかのどちらかに該当すれば十分です。

volume < 0 || volume > 20

比較すると、次の部分が反転しています。

範囲内の条件範囲外の条件
volume >= 0volume < 0
volume <= 20volume > 20
2つとも必要なので&&どちらかで範囲外なので||

図2:数直線で見る範囲内と範囲外

この図から分かること

範囲内は0から20までの連続した部分です。この部分に入るには、0以上と20以下の両方を満たさなければなりません。

範囲外は数直線の左右に分かれています。0未満か20より大きいかのどちらかに該当すれば範囲外なので、||を使います。

否定演算子!で条件式全体を反転する

比較演算子を一つずつ書き換えず、条件式の前に!を付けて反転することもできます。

!(volume >= 0 && volume <= 20)

丸括弧内は、volumeが0以上20以下という範囲内の条件です。その前に!を付けることで、範囲内ではないという意味になります。

!は直後の条件を否定する演算子です。複数の条件をまとめて反転するときは、対象となる条件式全体を丸括弧で囲みます。

否定演算子で音量の範囲外を判定するプログラム

ファイル名:8_5_3.c

#include <stdio.h>

int main(void)
{
    int volume;

    printf("音量設定値を入力してください(0~20)> ");
    scanf("%d", &volume);

    /* 範囲内の条件全体を!で否定する */
    if (!(volume >= 0 && volume <= 20)) {
        printf("音量設定値は範囲外です。\n");
    } else {
        printf("音量設定値は有効です。\n");
    }

    return 0;
}
実行結果の例
音量設定値を入力してください(0~20)> -3
音量設定値は範囲外です。
プログラムの詳しい解説

-3はvolume >= 0を満たさないため、丸括弧内の条件式は偽になります。

その偽を!で反転すると真になるので、ifブロックが実行されます。

反対に10を入力した場合、丸括弧内は真です。!によって偽へ反転されるため、elseブロックが実行されます。

次の2つの条件式は同じ意味です。

!(volume >= 0 && volume <= 20)
volume < 0 || volume > 20

!を使うと条件全体をそのまま反転できます。ただし、否定や丸括弧が多くなると読み取りにくくなることもあります。範囲外を直接書いたほうが分かりやすい場合は、volume < 0 || volume > 20を選んでも構いません。

比較演算子を反転する基本ルール

比較演算子には、それぞれ反対の意味になる組み合わせがあります。

元の条件反転した条件意味
A == BA != B等しいを等しくないへ反転
A != BA == B等しくないを等しいへ反転
A < BA >= Bより小さいを以上へ反転
A <= BA > B以下をより大きいへ反転
A > BA <= Bより大きいを以下へ反転
A >= BA < B以上をより小さいへ反転

たとえばage < 20を反転すると、age >= 20になります。

20未満ではない範囲には20も含まれるため、単純にage > 20へ変えるのではありません。境界値がどちらに含まれるかを確認することが大切です。

論理演算子を反転するルール

複数の条件をまとめて反転すると、&&と||も入れ替わります。

元の条件反転した条件
A && B!A || !B
A || B!A && !B

これは、次のように読み替えると理解しやすくなります。

  • AとBを両方満たすのではない
  • Aを満たさない、またはBを満たさない
  • AかBのどちらかを満たすのではない
  • AもBも満たさない

実際の範囲条件に当てはめると、次のようになります。

!(value >= 10 && value <= 30)

この条件は、次の条件と同じ意味です。

value < 10 || value > 30

範囲内をif側に置いた料金判定

次は、年齢が指定範囲内であることをif側で判定します。

ここでは、博物館のジュニア料金を6歳以上18歳以下の利用者に適用するものとします。

ジュニア料金の対象年齢を判定するプログラム

ファイル名:8_5_4.c

#include <stdio.h>

int main(void)
{
    int age;

    printf("年齢を入力してください > ");
    scanf("%d", &age);

    /* 6歳以上18歳以下ならジュニア料金の対象 */
    if (age >= 6 && age <= 18) {
        printf("ジュニア料金の対象です。\n");
    } else {
        printf("ジュニア料金の対象ではありません。\n");
    }

    return 0;
}
実行結果の例
年齢を入力してください > 18
ジュニア料金の対象です。
プログラムの詳しい解説

ジュニア料金の条件は6歳以上18歳以下なので、条件式は次のようになります。

age >= 6 && age <= 18

下限の6歳を含めるために>=を使い、上限の18歳も含めるために<=を使っています。

さらに、下限と上限の両方を満たす必要があるので、2つの条件を&&でつないでいます。

18を入力した場合、age >= 6とage <= 18はどちらも真です。そのため、ジュニア料金の対象としてifブロックが実行されます。

範囲外をif側に置いた利用時間判定

今度は、利用できない時間帯をif側で先に判定します。

施設を利用できる時間帯を9時以上18時未満とすると、利用時間外は次のようになります。

  • 9時未満
  • 18時以上
利用時間外を先に判定するプログラム

ファイル名:8_5_5.c

#include <stdio.h>

int main(void)
{
    int hour;

    printf("利用開始時刻を整数で入力してください > ");
    scanf("%d", &hour);

    /* 利用可能時間は9時以上18時未満 */
    if (hour < 9 || hour >= 18) {
        printf("現在は利用時間外です。\n");
    } else {
        printf("現在は利用可能な時間です。\n");
    }

    return 0;
}
実行結果の例
利用開始時刻を整数で入力してください > 18
現在は利用時間外です。
プログラムの詳しい解説

利用可能な時間を先に書くと、次の条件になります。

hour >= 9 && hour < 18

この条件を反転すると、利用時間外の条件になります。

hour < 9 || hour >= 18

利用可能な条件では18時未満となっているため、18時は含まれません。その反対側ではhour >= 18となり、18時を利用時間外に含めます。

入力時刻判定実行される側
8利用時間外if
9利用可能else
17利用可能else
18利用時間外if

図3:境界を含めて条件を反転する流れ

この図から分かること

条件式を反転するときは、演算子の向きだけでなく、境界値をどちらに含めるかも変化します。

hour < 18の反転はhour > 18ではなく、hour >= 18です。18未満ではない範囲には18も含まれるからです。

また、利用可能な条件では下限と上限の両方を満たすため&&を使いますが、利用時間外ではどちらか一方に該当すればよいため||を使います。

複数の条件を組み合わせたうるう年判定

うるう年の判定では、複数の条件を&&と||で組み合わせます。

判定基準は次のとおりです。

  • 400で割り切れる年はうるう年
  • 400で割り切れなくても、4で割り切れて100で割り切れない年はうるう年
  • それ以外は平年
1年の日数をうるう年の条件から判定するプログラム

ファイル名:8_5_6.c

#include <stdio.h>

int main(void)
{
    int year;

    printf("西暦年を入力してください > ");
    scanf("%d", &year);

    /* うるう年なら366日、平年なら365日と表示する */
    if ((year % 400 == 0) ||
        (year % 4 == 0 && year % 100 != 0)) {
        printf("%d年はうるう年で、1年は366日です。\n", year);
    } else {
        printf("%d年は平年で、1年は365日です。\n", year);
    }

    return 0;
}
実行結果の例

400で割り切れる年を入力した場合は、うるう年になります。

西暦年を入力してください > 2000
2000年はうるう年で、1年は366日です。

100で割り切れても400で割り切れない年は、平年になります。

西暦年を入力してください > 2100
2100年は平年で、1年は365日です。
プログラムの詳しい解説

最初の条件は、yearが400で割り切れるかを調べています。

year % 400 == 0

2000は400で割り切れるため、この条件だけでうるう年と判定できます。

もう一方では、4で割り切れることと100で割り切れないことを&&で組み合わせています。

year % 4 == 0 && year % 100 != 0

この2つのうるう年条件は、どちらか一方を満たせばよいため、||で接続します。

(year % 400 == 0) ||
(year % 4 == 0 && year % 100 != 0)

2100は4で割り切れますが、100でも割り切れます。また、400では割り切れません。したがって、うるう年の条件式全体が偽になり、elseブロックが実行されます。

条件式が長くなる場合は、意味のまとまりごとに丸括弧を付けると読みやすくなります。どの条件を&&でまとめ、どのまとまりを||で接続しているのかが見えやすくなり、反転や修正をするときの間違いも防ぎやすくなります。