C言語プログラミング基礎:if else による条件分岐の基本


C言語プログラミング基礎:if else による条件分岐の基本

第1章:プログラミングにおける「条件分岐」とは何か?

1.1 プログラムの実行フロー

コンピュータプログラムは基本的に、書かれた命令を上から順番に実行していきます。これは「順次実行」と呼ばれる最も単純な実行フローです。例えば、変数に値を代入し、その値を画面に表示するという一連の処理は、特別な指示がない限りこの順次実行に従います。

“`c

include

int main() {
int x = 10; // 1. xに10を代入
int y = 20; // 2. yに20を代入
int sum = x + y; // 3. xとyを足してsumに代入
printf(“合計: %d\n”, sum); // 4. sumの値を表示
return 0;
}
“`

この例では、コメントで示されている順序で命令が実行されます。特別な状況が発生しない限り、この流れは変わりません。

1.2 なぜ条件分岐が必要なのか?

しかし、現実世界のタスクや、より複雑な処理をプログラムで実現しようとすると、常に同じ手順で処理を進めるわけにはいかなくなります。状況に応じて異なる処理を行いたい、あるいは特定の条件が満たされたときだけ処理を実行したい、という要求が必ず発生します。

例えば:

  • ユーザーが入力した数値が正の場合と負の場合で異なるメッセージを表示したい。
  • 在庫数がゼロになったら「在庫切れ」と表示し、注文を受け付けないようにしたい。
  • テストの点数が80点以上なら「合格」、それ未満なら「不合格」と判定したい。
  • ユーザーの年齢が18歳以上なら成人向けコンテンツを表示し、それ未満なら表示しない。

このような「もし~ならば、この処理を行う」「そうでなければ、別の処理を行う」といった判断や選択をプログラムに行わせる仕組みが、「条件分岐」です。条件分岐は、プログラムに柔軟性とインテリジェンスをもたらすための最も基本的な制御構造の一つです。

1.3 C言語における条件分岐の主な方法

C言語で条件分岐を実現するための主な方法は以下の通りです。

  • if 文: 指定した条件が真(true)の場合にのみ処理を実行します。
  • if-else 文: 指定した条件が真の場合に一方の処理を、偽(false)の場合に他方の処理を実行します。
  • if-else if-else 文: 複数の条件を順番にチェックし、最初に真となった条件に対応する処理を実行します。どの条件も偽だった場合の処理も指定できます。
  • switch 文: 一つの式の値に基づいて、複数の固定された値(ケース)のいずれかに一致するかどうかを判定し、対応する処理を実行します。これは、特定の変数の値によって処理を分けたい場合に便利です。
  • 条件演算子 (? :): シンプルな if-else による代入や値の決定を簡潔に記述するための演算子です。

本記事では、これらのうち、最も基本であり、かつ最も応用範囲が広い if 文、if-else 文、そしてそれらを組み合わせた if-else if-else 文、さらに「ネスト(入れ子)」について、その構文、使い方、具体例、そしてよくある落とし穴までを徹底的に解説します。

第2章:基本中の基本 – if

2.1 if 文の構文

if 文は、最も単純な条件分岐です。指定した「条件」が真(true)である場合に限り、特定の処理を実行します。

if 文の基本的な構文は以下の通りです。

c
if (条件式) {
// 条件式が真の場合に実行される文;
// 条件式が真の場合に実行される別の文;
...
}

  • if というキーワードで始まります。
  • 丸括弧 () の中に「条件式」を書きます。
  • 波括弧 {} の中に、条件式が真と評価された場合に実行したい処理(文)を記述します。この波括弧で囲まれた部分を「ブロック」と呼びます。

条件式について

条件式は、評価結果が「真」または「偽」のいずれかになる式です。C言語において、条件式は以下のように評価されます。

  • 0以外の値: 「真(true)」と評価されます。
  • 0: 「偽(false)」と評価されます。

したがって、条件式としては、比較演算の結果(例: a > b)や論理演算の結果(例: a > 0 && b > 0)、あるいは単なる数値(例: if (5) は真、if (0) は偽)などを使うことができます。しかし、ほとんどの場合は比較や論理演算の結果を利用します。

2.2 比較演算子と等価演算子

条件式を構成する上で頻繁に利用されるのが「比較演算子」と「等価演算子」です。

  • 比較演算子:

    • < : 左辺が右辺より小さい
    • > : 左辺が右辺より大きい
    • <= : 左辺が右辺以下
    • >= : 左辺が右辺以上
  • 等価演算子:

    • == : 左辺と右辺が等しい (代入演算子の = と間違えないように注意!)
    • != : 左辺と右辺が等しくない

これらの演算子は、比較の結果として「真(1)」または「偽(0)」の整数値を返します。この返された値が if 文の条件式として評価されます。

例:
* 5 > 3 は真なので 1 を返します。
* 10 == 10 は真なので 1 を返します。
* 5 < 2 は偽なので 0 を返します。
* 8 != 8 は偽なので 0 を返します。

2.3 if 文の簡単な例

数値が正かどうかを判定する簡単な例を見てみましょう。

“`c

include

int main() {
int number = 15;

// numberが0より大きい(正)か?
if (number > 0) {
    printf("入力された数値 %d は正です。\n", number);
}

printf("プログラム終了。\n");

return 0;

}
“`

実行結果:

入力された数値 15 は正です。
プログラム終了。

解説:

  1. int number = 15;number という整数型変数に 15 を代入します。
  2. if (number > 0) で条件式 number > 0 を評価します。15 > 0 は真(1)です。
  3. 条件式が真なので、if の後に続く波括弧 {} 内の文、つまり printf("入力された数値 %d は正です。\n", number); が実行されます。
  4. if ブロックの実行が完了したら、その後の printf("プログラム終了。\n"); が実行されます。

もし number の値が -5 だった場合、条件式 number > 0-5 > 0 となり、これは偽(0)です。この場合、if の波括弧内の printf はスキップされ、直接 printf("プログラム終了。\n"); が実行されます。

“`c

include

int main() {
int number = -5;

// numberが0より大きい(正)か?
if (number > 0) {
    printf("入力された数値 %d は正です。\n", number); // これは実行されない
}

printf("プログラム終了。\n"); // これは常に実行される

return 0;

}
“`

実行結果:

プログラム終了。

2.4 if 文と波括弧 {}

if 文の波括弧 {} は、条件が真だった場合に複数の文をまとめて実行するために使われます。もし実行したい文が1つだけであれば、波括弧を省略することも 可能 です。

“`c

include

int main() {
int score = 90;

// 波括弧を省略した場合 (1つの文のみ)
if (score >= 80)
    printf("素晴らしい点数です!\n"); // if文に続く最初の1文のみが対象

printf("スコア: %d\n", score); // これはif文の対象外

// 別の例 (波括弧を省略した場合)
int x = 10;
if (x > 5)
    printf("xは5より大きい。\n");
    printf("この行は常に実行されます!\n"); // 間違えやすいポイント!これはifの対象ではない!

return 0;

}
“`

実行結果:

素晴らしい点数です!
スコア: 90
xは5より大きい。
この行は常に実行されます!

解説:

最初の if (score >= 80) の例では、score >= 80 が真なので、直後の1文である printf("素晴らしい点数です!\n"); が実行されます。その次の printf("スコア: %d\n", score);if 文の一部ではないため、条件に関わらず常に実行されます。

2番目の if (x > 5) の例は、波括弧を省略した場合の 注意点 を示しています。if (x > 5) の条件は真ですが、if 文の対象となるのは直後の1文、つまり printf("xは5より大きい。\n"); だけです。その次の printf("この行は常に実行されます!\n");if 文の対象外であり、条件に関わらず常に実行されます。

このように、波括弧を省略すると、どの文が if 文の制御下にあるのかが視覚的に分かりにくくなり、意図しないバグの原因となります。特に、後から文を追加しようとした際に、波括弧を付け忘れると、追加した文が条件分岐の対象にならないというミスを犯しやすくなります。

推奨される書き方:

実行する文が1つであっても、常に波括弧 {} を使用することを強く推奨します。これにより、コードの可読性が向上し、意図しない挙動を防ぐことができます。

“`c

include

int main() {
int score = 90;

// 推奨される書き方 (1つの文でも波括弧を使う)
if (score >= 80) {
    printf("素晴らしい点数です!\n");
}

printf("スコア: %d\n", score); // これはif文の対象外

return 0;

}
“`

この推奨スタイルは、コードの保守性を高める上で非常に重要です。

第3章:真と偽で処理を分ける – if-else

3.1 if-else 文の構文

if 文は「条件が真なら実行、偽なら何もしない」という動きでした。一方、if-else 文は、「条件が真ならAの処理、偽ならBの処理」というように、真の場合と偽の場合で実行する処理を明確に分けたい場合に用います。

if-else 文の基本的な構文は以下の通りです。

c
if (条件式) {
// 条件式が真の場合に実行される文;
// (ifブロック)
} else {
// 条件式が偽の場合に実行される文;
// (elseブロック)
}

  • if (条件式) の部分は if 文と同じです。
  • else というキーワードが if ブロックの直後に続きます。
  • else の後に続く波括弧 {} 内に、条件式が偽と評価された場合に実行したい処理を記述します。

if ブロックまたは else ブロック内の文が1つだけであっても、可読性と安全性のために波括弧を省略しないことを推奨します。

3.2 if-else 文の実行フロー

if-else 文の実行フローは以下のようになります。

  1. まず if の丸括弧内の「条件式」が評価されます。
  2. 条件式が「真(true)」と評価された場合:
    • if の後に続く波括弧 {} 内のブロック(if ブロック)が実行されます。
    • else の後に続く波括弧 {} 内のブロック(else ブロック)は 完全にスキップ されます。
    • if-else 文全体の実行が終了し、その後の文に移ります。
  3. 条件式が「偽(false)」と評価された場合:
    • if の後に続く波括弧 {} 内のブロック(if ブロック)は 完全にスキップ されます。
    • else の後に続く波括弧 {} 内のブロック(else ブロック)が実行されます。
    • if-else 文全体の実行が終了し、その後の文に移ります。

つまり、if ブロックと else ブロックのうち、必ずどちらか一方のみ が実行されます。両方が実行されることはありませんし、両方とも実行されないこともありません。

3.3 if-else 文の例

数値が偶数か奇数かを判定する例を見てみましょう。ある整数が偶数であるかどうかは、その整数を2で割った余りが0であるかどうかで判定できます。余りを求めるには % (剰余演算子) を使います。

“`c

include

int main() {
int number;

printf("整数を入力してください: ");
scanf("%d", &number);

// numberを2で割った余りが0か? (偶数か?)
if (number % 2 == 0) {
    printf("%d は偶数です。\n", number);
} else {
    printf("%d は奇数です。\n", number);
}

printf("判定終了。\n");

return 0;

}
“`

実行例1 (偶数を入力):

整数を入力してください: 10
10 は偶数です。
判定終了。

実行例2 (奇数を入力):

整数を入力してください: 7
7 は奇数です。
判定終了。

解説:

  1. ユーザーに整数を入力させ、number 変数に格納します。
  2. if (number % 2 == 0) で条件式 number % 2 == 0 を評価します。
    • 入力が 10 の場合、10 % 20 です。0 == 0 は真(1)なので、if ブロックが実行されます。else ブロックはスキップされます。
    • 入力が 7 の場合、7 % 21 です。1 == 0 は偽(0)なので、if ブロックはスキップされ、else ブロックが実行されます。
  3. どちらかのブロック内の printf が実行された後、if-else 文全体が終了し、その後の printf("判定終了。\n"); が実行されます。

この例のように、if-else 文は二者択一の判断を行う場合に非常に適しています。

3.4 if-else とブロック内の複数の文

if ブロックまたは else ブロックには、波括弧 {} を使うことで複数の文を含めることができます。

“`c

include

int main() {
int age = 20;

if (age >= 18) {
    printf("年齢は %d 歳です。\n", age);
    printf("あなたは成人です。\n");
    printf("成人の権利と義務があります。\n");
} else {
    printf("年齢は %d 歳です。\n", age);
    printf("あなたは未成年です。\n");
    printf("保護者の同意が必要です。\n");
}

printf("年齢判定プログラム終了。\n");

return 0;

}
“`

実行結果:

年齢は 20 歳です。
あなたは成人です。
成人の権利と義務があります。
年齢判定プログラム終了。

この例では、age >= 18 という条件が真なので、if ブロック内の3つの printf 文が順に実行されます。else ブロック内の文はスキップされます。もし age16 だったら、if ブロックがスキップされ、else ブロック内の3つの printf 文が実行されます。

波括弧 {} を使うことで、複数の文を一つのまとまり(ブロック)として扱い、「この条件が満たされたら、この一連の処理を行う」と明確に定義できます。

第4章:複数の条件を順に判定 – if-else if-else

4.1 if-else if-else 文の構文

これまでの if 文は「条件が真なら実行」、if-else 文は「真ならA、偽ならB」という二者択一でした。しかし、実際には3つ以上の選択肢の中から一つを選びたい場合があります。例えば、テストの点数によってA, B, C, D, Fの5段階で評価したい、といったケースです。

このような場合、if-else if-else という形式を使います。これは、複数の ifelse if を連ねて、条件を順番にチェックしていく構造です。

if-else if-else 文の基本的な構文は以下の通りです。

c
if (条件式1) {
// 条件式1が真の場合に実行される文;
} else if (条件式2) {
// 条件式1が偽で、かつ条件式2が真の場合に実行される文;
} else if (条件式3) {
// 条件式1, 2が偽で、かつ条件式3が真の場合に実行される文;
}
// ... 必要に応じてelse if を複数追加 ...
else {
// 上記のどの条件式も偽だった場合に実行される文;
// (elseブロックは省略可能)
}

  • 最初の if (条件式1) で最初の条件をチェックします。
  • もし最初の条件が偽であれば、次の else if (条件式2) の条件式がチェックされます。
  • もし else if (条件式2) の条件も偽であれば、さらに次の else if がチェックされます。
  • このように、上から順番に条件式が評価されていきます。
  • 最初に真と評価された条件式に対応するブロックだけが実行 され、それ以降の else ifelse の部分はすべてスキップされます。
  • もし全ての ifelse if の条件式が偽だった場合、一番最後の else ブロック(存在する場合)が実行されます。
  • 最後の else ブロックは省略可能です。省略した場合、どの条件も真にならなかったときは何も実行されずに if-else if 文全体が終了します。

4.2 if-else if-else 文の実行フロー

実行フローは以下の通りです。

  1. 条件式1 が評価されます。
  2. 条件式1 が真なら、最初の if ブロックが実行され、if-else if-else 文全体が終了します。
  3. 条件式1 が偽なら、条件式2 が評価されます。
  4. 条件式2 が真なら、最初の else if ブロックが実行され、if-else if-else 文全体が終了します。
  5. 条件式2 が偽なら、次の else if (あれば) の条件式が評価されます。
  6. このプロセスが、真となる条件式が見つかるか、あるいは全ての else if の条件式が偽となるまで繰り返されます。
  7. 全ての if および else if の条件式が偽だった場合、最後の else ブロックが存在すればそれが実行され、if-else if-else 文全体が終了します。
  8. 全ての条件が偽で、かつ最後の else ブロックが存在しない場合、何も実行されずに if-else if-else 文全体が終了します。

重要なのは、真となる条件が複数あったとしても、最初に真となった条件に対応するブロックだけが実行され、他のブロックは一切実行されない という点です。

4.3 if-else if-else 文の例 (点数による成績判定)

テストの点数に応じてA, B, C, D, Fの成績を判定する例です。

  • 90点以上:A
  • 80点以上90点未満:B
  • 70点以上80点未満:C
  • 60点以上70点未満:D
  • 60点未満:F

“`c

include

int main() {
int score;

printf("テストの点数を入力してください (0-100): ");
scanf("%d", &score);

char grade; // 成績を格納する変数

if (score >= 90) {
    grade = 'A';
} else if (score >= 80) { // score >= 80 && score < 90 (最初の条件が偽の場合なのでscore < 90は暗黙的に満たされる)
    grade = 'B';
} else if (score >= 70) { // score >= 70 && score < 80
    grade = 'C';
} else if (score >= 60) { // score >= 60 && score < 70
    grade = 'D';
} else { // score < 60 (上記のどの条件も満たされない場合)
    grade = 'F';
}

printf("あなたの成績は %c です。\n", grade);

return 0;

}
“`

実行例1 (85点を入力):

テストの点数を入力してください (0-100): 85
あなたの成績は B です。

解説:

  1. score85 が入力されます。
  2. if (score >= 90): 85 >= 90 は偽です。
  3. 最初の条件が偽なので、次の else if (score >= 80) が評価されます。85 >= 80 は真です。
  4. この条件が真なので、このブロック内 grade = 'B'; が実行されます。
  5. 真となる条件が見つかり処理が実行されたため、これ以降の else ifelse は全てスキップされ、if-else if-else 文全体が終了します。
  6. 最後に printf("あなたの成績は %c です。\n", grade); で結果が表示されます。

実行例2 (55点を入力):

テストの点数を入力してください (0-100): 55
あなたの成績は F です。

解説:

  1. score55 が入力されます。
  2. if (score >= 90): 55 >= 90 は偽です。
  3. else if (score >= 80): 55 >= 80 は偽です。
  4. else if (score >= 70): 55 >= 70 は偽です。
  5. else if (score >= 60): 55 >= 60 は偽です。
  6. 全ての if および else if の条件が偽だったため、最後の else ブロックが実行されます。grade = 'F'; が実行されます。
  7. 最後に結果が表示されます。

4.4 条件式の順序の重要性

if-else if チェーンでは、条件式が上から順番に評価されるため、その順序が非常に重要になります。特に、複数の条件が同時に真になりうる場合、どの条件を先に書くかで結果が変わる可能性があります。

上記の点数判定の例で、もし条件の順序を逆に書いてしまったらどうなるでしょうか?

c
// 悪い例:条件の順序が逆
if (score >= 60) { // 60点以上の全ての人がDになる?
grade = 'D';
} else if (score >= 70) { // この条件は決して真にならない!
grade = 'C';
} else if (score >= 80) { // この条件も決して真にならない!
grade = 'B';
} else if (score >= 90) { // この条件も決して真にならない!
grade = 'A';
} else {
grade = 'F';
}

このコードでは、例えば score85 の場合を考えます。
1. if (score >= 60): 85 >= 60 は真です。
2. この条件が真なので、grade = 'D'; が実行され、それ以降の else if は全てスキップされます。
3. 結果として、85点なのに成績は’D’と判定されてしまいます。

なぜこうなるかというと、score >= 60 という条件は、70点、80点、90点といった点数でも真になるためです。より広い範囲をカバーする条件を先に書いてしまうと、より狭い範囲の条件(例えば90点以上)がチェックされる前に処理が決定されてしまうのです。

したがって、複数の範囲やカテゴリを判定する if-else if チェーンでは、通常はより厳密な条件狭い範囲の条件を先に書き、徐々に緩やかな条件広い範囲の条件へと進めるのが適切な順序となります。点数判定の場合は、「90点以上」→「80点以上」→「70点以上」→「60点以上」のように、点数の高い方から低い方へ(あるいは低い方から高い方へ、ただしその場合は範囲の記述方法を工夫する必要がある)順に並べるのが一般的です。

4.5 最後の else の重要性 (省略した場合)

if-else if チェーンの最後の else ブロックは省略可能ですが、多くの場合は含めることを検討すべきです。

  • 最後の else がある場合: 定義したどの条件にも一致しなかった場合の「デフォルト」の処理を提供できます。上記の成績判定の例では、「60点未満」というどの条件にも当てはまらない場合に F と判定する役割を果たしています。
  • 最後の else がない場合: 定義したどの条件にも一致しなかった場合、if-else if 文全体で何も処理が実行されません。これは、特定の条件にだけ反応したい場合には適切ですが、すべての可能性をカバーしたい場合には、漏れがないか確認が必要です。

例えば、入力された月に応じて季節を表示するプログラムを考えてみましょう。

“`c

include

int main() {
int month;

printf("月を入力してください (1-12): ");
scanf("%d", &month);

if (month == 12 || month == 1 || month == 2) {
    printf("%d月は冬です。\n", month);
} else if (month >= 3 && month <= 5) {
    printf("%d月は春です。\n", month);
} else if (month >= 6 && month <= 8) {
    printf("%d月は夏です。\n", month);
} else if (month >= 9 && month <= 11) {
    printf("%d月は秋です。\n", month);
}
// この例では、1~12月の全てのケースをカバーしているため、
// 理論上はここに到達しないが、不正な入力(例: 13月)には対応できない。

printf("季節判定終了。\n");

return 0;

}
“`

このコードは、入力が1~12の範囲内であれば正しく動作します。しかし、もしユーザーが誤って 13 を入力したらどうなるでしょうか?

実行例 (不正な入力を入力):

月を入力してください (1-12): 13
季節判定終了。

どの ifelse if も真にならないため、何も季節が表示されません。このような不正な入力や予期しない値に対応するためには、最後の else ブロックでエラーメッセージを表示するなどの処理を加えることが有効です。

“`c

include

int main() {
int month;

printf("月を入力してください (1-12): ");
scanf("%d", &month);

if (month == 12 || month == 1 || month == 2) {
    printf("%d月は冬です。\n", month);
} else if (month >= 3 && month <= 5) {
    printf("%d月は春です。\n", month);
} else if (month >= 6 && month <= 8) {
    printf("%d月は夏です。\n", month);
} else if (month >= 9 && month <= 11) {
    printf("%d月は秋です。\n", month);
} else {
    // 1-12以外の不正な入力の場合
    printf("%d は無効な月です。\n", month);
}

printf("季節判定終了。\n");

return 0;

}
“`

このように最後の else を使うことで、プログラムが予期しない状況にどう対処するかを定義できます。

第5章:条件分岐の重ね合わせ – ネスト(入れ子)

5.1 ネストされた if-else 文とは?

条件分岐の中にさらに別の条件分岐を記述することを、「ネスト(nest)」または「入れ子」と呼びます。if ブロックや else ブロックの中に、別の if 文や if-else 文、if-else if 文などを書くことができます。

ネストされた if-else 文の基本的な構造は以下のようになります。

“`c
if (外側の条件式) {
// 外側の条件が真の場合に実行されるブロック

if (内側の条件式1) {
    // 外側が真で、かつ内側1が真の場合に実行される文;
} else {
    // 外側が真で、かつ内側1が偽の場合に実行される文;
}

// 外側の条件が真の場合に実行される他の文;

} else {
// 外側の条件が偽の場合に実行されるブロック

if (内側の条件式2) {
    // 外側が偽で、かつ内側2が真の場合に実行される文;
}
// 内側のelseは省略可能

// 外側の条件が偽の場合に実行される他の文;

}
“`

ネストは、複数の独立した条件が組み合わさって一つの判断を下す必要がある場合に役立ちます。例えば、「ユーザーがログインしている かつ 管理者権限を持っている場合に、特別な設定メニューを表示する」といったケースです。

5.2 ネストの例

ある数値が正の偶数であるかどうかを判定する例を考えます。これは、「数値が正である」という条件と、「数値が偶数である」という条件の両方が満たされる必要があります。

“`c

include

int main() {
int number;

printf("整数を入力してください: ");
scanf("%d", &number);

if (number > 0) { // 外側の条件: 数値が正か?
    printf("%d は正の数です。\n", number);
    if (number % 2 == 0) { // 内側の条件: 数値が偶数か? (正である場合にのみ評価される)
        printf("そして、偶数です。\n");
    } else {
        printf("そして、奇数です。\n");
    }
} else { // 外側の条件が偽の場合 (数値が0以下)
    printf("%d は正の数ではありません。\n", number);
    if (number == 0) { // 内側の条件: 数値が0か? (正でない場合にのみ評価される)
        printf("具体的にはゼロです。\n");
    } else { // number < 0
        printf("具体的には負の数です。\n");
    }
}

printf("判定終了。\n");

return 0;

}
“`

実行例1 (正の偶数):

整数を入力してください: 10
10 は正の数です。
そして、偶数です。
判定終了。

解説: number10 の場合。
1. if (number > 0): 10 > 0 は真。外側の if ブロックに入ります。
2. printf("%d は正の数です。\n", number); が実行されます。
3. 内側の if (number % 2 == 0) が評価されます。10 % 2 == 0 は真。内側の if ブロックに入ります。
4. printf("そして、偶数です。\n"); が実行されます。
5. 内側の if-else が終了し、外側の if ブロック内の残りの処理(この例では他に無し)を実行します。
6. 外側の if ブロックが終了し、外側の else ブロックはスキップされ、if-else 文全体が終了します。

実行例2 (負の奇数):

整数を入力してください: -7
-7 は正の数ではありません。
具体的には負の数です。
判定終了。

解説: number-7 の場合。
1. if (number > 0): -7 > 0 は偽。外側の else ブロックに入ります。
2. printf("%d は正の数ではありません。\n", number); が実行されます。
3. 内側の if (number == 0) が評価されます。-7 == 0 は偽。内側の else ブロックに入ります。
4. printf("具体的には負の数です。\n"); が実行されます。
5. 内側の if-else が終了し、外側の else ブロック内の残りの処理(この例では他に無し)を実行します。
6. 外側の else ブロックが終了し、if-else 文全体が終了します。

このようにネストを使うと、「条件Aが真であるという前提のもとで、さらに条件Bを判定する」といった複雑な条件判断を表現できます。

5.3 可読性とインデント

ネストされた if-else 文を書く際には、インデント(字下げ)を適切に行うことが非常に重要です。適切なインデントは、どの ifelse が対応しているのか、どの文がどのブロックに属しているのかを視覚的に分かりやすくします。

“`c
// 良い例 (適切なインデント)
if (condition1) {
// code for condition1
if (condition2) {
// code for condition1 AND condition2
} else {
// code for condition1 AND NOT condition2
}
} else {
// code for NOT condition1
if (condition3) {
// code for NOT condition1 AND condition3
}
}

// 悪い例 (不適切なインデント)
if (condition1) {
// code for condition1
if (condition2) {
// code for condition1 AND condition2
} else {
// code for condition1 AND NOT condition2
}
} else {
// code for NOT condition1
if (condition3) {
// code for NOT condition1 AND condition3
}
}
“`

悪い例のようにインデントが崩れていると、コードの構造が把握しにくく、バグの発見や修正が困難になります。現代のエディタやIDEは自動で適切なインデントを行ってくれる機能があるので、積極的に利用しましょう。

5.4 ネストの深さ

ネストは強力な表現力を持つ一方で、あまり深くしすぎるとコードが非常に読みにくく、理解しにくくなります。一般的に、ネストの深さは2段階か3段階までにとどめるのが望ましいとされています。

もしネストが深くなりすぎる場合は、以下の対策を検討しましょう。

  1. 条件式の組み合わせ: ネストされた条件を論理演算子 (&&, ||) を使って一つの条件式にまとめることで、ネストを解消できる場合があります。(例: if (cond1) { if (cond2) { ... } }if (cond1 && cond2) { ... } にする)
  2. 関数への分割: 内側のブロックの処理を独立した関数として抽出し、その関数を呼び出すようにすることで、コードの見通しを良くします。
  3. 早期リターン/終了: 条件が満たされない場合にすぐに関数を終了(return)させることで、else ブロックや深いネストを避けることができます。

5.5 ぶら下がりelse問題 (Dangling else)

ネストに関連して、C言語には「ぶら下がり else 問題 (dangling else problem)」という、見かけと実際の挙動が異なる可能性のある注意点があります。これは、波括弧 {} を省略した場合に発生し得ます。

c
if (condition1)
if (condition2)
printf("Both conditions are true.\n");
else // このelseはどちらのifに対応する?
printf("Condition 1 is false.\n");

このコードを見たとき、人間はインデントから「最初の if (condition1)else が対応している」と誤解しやすいです。しかし、C言語の文法規則では、else は直前の対応する if とペアになる、というルールがあります(ただし、波括弧で囲まれたブロックは一つの文として扱われます)。

したがって、上記のコードの else は、外側の if (condition1) ではなく、内側の if (condition2) に対応すると解釈されます。

実際の解釈(コンパイラによる解釈)は以下のようになります。

c
if (condition1) { // 外側のif
if (condition2) { // 内側のif
printf("Both conditions are true.\n");
} else { // !!! これは内側のifに対するelse !!!
printf("Condition 1 is false.\n"); // このメッセージは「condition1が真で、condition2が偽の場合」に表示される!
}
}
// 外側のifに対するelseは存在しない

この例で condition1 が偽の場合、外側の if ブロック全体がスキップされるため、else ブロックも実行されません。"Condition 1 is false." と表示されるのは、condition1 が真であり、かつ condition2 が偽である場合です。これは、インデントから期待される挙動と異なります。

この問題を避けるための最も確実な方法は、常に波括弧 {} を使用する ことです。波括弧を使うことで、if 文や else 文の範囲が明確になり、else がどの if に対応するかが曖昧になることを防げます。

c
// ぶら下がりelse問題を避けるための書き方 (波括弧を使用)
if (condition1) {
if (condition2) {
printf("Both conditions are true.\n");
} // 内側のif-else終了
} else { // 外側のifに対応するelse
printf("Condition 1 is false.\n");
}

このように波括弧を適切に使うことで、コードの見かけと実際の挙動が一致し、バグを減らすことができます。

第6章:条件式をもっと詳しく – 論理演算子と真偽値

6.1 論理演算子

これまでの例では、a > bx == 0 といった単一の比較・等価演算子を使った条件式を見てきました。しかし、実際には複数の条件を組み合わせて判断したいことがよくあります。そのような場合に「論理演算子」を使用します。

C言語の主な論理演算子は以下の3つです。

  • && (論理AND): 左辺の式と右辺の式の 両方 が真(非ゼロ)の場合に真(1)となります。どちらか一方でも偽(ゼロ)なら偽(0)となります。
  • || (論理OR): 左辺の式と右辺の式の 少なくとも一方 が真(非ゼロ)の場合に真(1)となります。両方とも偽(ゼロ)の場合にのみ偽(0)となります。
  • ! (論理NOT): 右辺の式の真偽を反転させます。右辺が真(非ゼロ)なら偽(0)に、右辺が偽(ゼロ)なら真(1)になります。

これらの演算子も、比較・等価演算子と同様に、結果として整数値 1 (真) または 0 (偽) を返します。

論理演算子の例:

“`c

include

int main() {
int age = 25;
int income = 300000;
int has_job = 1; // 1は真、0は偽とみなす

// 論理AND (&&) の例
// 年齢が20歳以上 かつ 収入が25万円以上 かつ 仕事がある
if (age >= 20 && income >= 250000 && has_job) {
    printf("ローン審査通過の可能性が高いです。\n");
}

// 論理OR (||) の例
// 年齢が65歳以上 または 18歳未満
if (age >= 65 || age < 18) {
    printf("シニア割引または学割の対象です。\n");
}

// 論理NOT (!) の例
// 仕事がない場合 (!has_job は has_job == 0 と等価)
if (!has_job) {
    printf("現在仕事を探しています。\n");
}

// NOTと組み合わせた例
// 年齢が20歳未満 ではない (つまり20歳以上) かつ 仕事がある
if (!(age < 20) && has_job) {
     printf("20歳以上で仕事があります。\n");
}


return 0;

}
“`

実行結果:

ローン審査通過の可能性が高いです。
20歳以上で仕事があります。

解説:

  • 最初の if では、age >= 20 (真), income >= 250000 (真), has_job (真) の全てが真なので、条件式全体が真となり、ブロック内のコードが実行されます。
  • 2番目の if では、age >= 65 (偽), age < 18 (偽) のどちらも偽なので、条件式全体が偽となり、ブロックはスキップされます。
  • 3番目の if では、!has_job が評価されます。has_job1 (真) なので、!10 (偽) となります。条件式全体が偽となり、ブロックはスキップされます。
  • 4番目の if では、!(age < 20) が評価されます。age < 20 (25 < 20) は偽(0)なので、!0 は真(1)となります。そして && has_job と組み合わされ、1 && 1 は真(1)なので、条件式全体が真となり、ブロックが実行されます。

論理演算子を使うことで、複数の条件を柔軟に組み合わせ、より複雑な状況判断をプログラムにさせることができます。

6.2 論理演算子の短絡評価

C言語では、論理AND (&&) と論理OR (||) 演算子は「短絡評価(Short-circuit evaluation)」を行います。これは、式の評価の途中で全体の真偽が確定した場合、それ以降のオペランド(演算対象)は評価されないという仕組みです。

  • && (論理AND): 左辺の式が偽(0)と評価された場合、全体の式は必ず偽(0)になるため、右辺の式は評価されません。
  • || (論理OR): 左辺の式が真(非ゼロ)と評価された場合、全体の式は必ず真(1)になるため、右辺の式は評価されません。

この短絡評価は、意図しないエラーを防ぐために利用されることがあります。

例:ポインタがNULLでないことを確認してから、そのポインタ経由でメンバにアクセスする。

“`c

include

include

struct Node {
int data;
struct Node *next;
};

int main() {
struct Node p = NULL;
// struct Node
p = (struct Node*)malloc(sizeof(struct Node));
// if (p != NULL) p->data = 100;

// pがNULLでないことを確認(左辺)してから、p->dataにアクセスする(右辺)
if (p != NULL && p->data > 0) {
    printf("データは正の値です。\n");
} else {
    printf("ポインタがNULLであるか、データが正の値ではありません。\n");
}

// もしpがNULLの場合、p->dataにアクセスするとエラーになる可能性がある
// if (p->data > 0 && p != NULL) { ... } // この順序は危険!

if (p != NULL) {
    free(p);
}

return 0;

}
“`

if (p != NULL && p->data > 0) という条件式では、もし pNULL であれば、左辺 p != NULL は偽(0)になります。論理ANDの短絡評価により、右辺 p->data > 0 は評価されません。これにより、NULLポインタに対してメンバアクセスを行おうとする実行時エラー(セグメンテーション違反など)を防ぐことができます。

もしこの順序が逆で if (p->data > 0 && p != NULL) となっていたら、pNULL の場合に p->data > 0 が評価されてしまい、エラーが発生する可能性が高くなります。

短絡評価は重要な挙動なので理解しておきましょう。

6.3 C言語における「真」と「偽」の再確認

改めて、C言語における「真」と「偽」の扱いは非常にシンプルです。

  • 偽(false): 厳密に整数値の 0 のみを偽として扱います。
  • 真(true): 0 以外の全ての値 を真として扱います。正の整数、負の整数、浮動小数点数(ただし浮動小数点数は比較に注意が必要)、非NULLポインタなど、0でなければ全て真です。

条件式では、その評価結果がこのルールに従って真偽が判断されます。

“`c

include

int main() {
int a = 10;
int b = 0;
int c = -5;
float d = 0.0;
float e = 3.14;
void ptr = NULL; // NULLは通常0で定義される
void
ptr2 = &a; // アドレスは通常非0

if (a) printf("aは真です (値: %d)\n", a); // 10は非0なので真
if (b) printf("bは真です (値: %d)\n", b); // 0なので偽 (この行は表示されない)
if (c) printf("cは真です (値: %d)\n", c); // -5は非0なので真
if (d) printf("dは真です (値: %f)\n", d); // 0.0は0なので偽 (この行は表示されない)
if (e) printf("eは真です (値: %f)\n", e); // 3.14は非0なので真
if (ptr) printf("ptrは真です (NULLではない)\n"); // NULLは通常0なので偽 (この行は表示されない)
if (ptr2) printf("ptr2は真です (NULLではない)\n"); // アドレスは通常非0なので真

if (!b) printf("!bは真です (bは偽)\n"); // !0 は 1 なので真
if (!a) printf("!aは真です (aは偽)\n"); // !10 は 0 なので偽 (この行は表示されない)

return 0;

}
“`

実行結果:

aは真です (値: 10)
cは真です (値: -5)
eは真です (値: 3.140000)
ptr2は真です (NULLではない)
!bは真です (bは偽)

このシンプルなルールを理解しておけば、C言語の条件判断の挙動を正確に予測できます。

6.4 条件式記述時の注意点・落とし穴

6.4.1 代入演算子 = と等価演算子 == の間違い

C言語の条件式で最も頻繁に発生するミスの一つが、比較を意図しているのに代入演算子 = を使ってしまうことです。

c
int x = 5;
// 意図: xが10と等しいか比較したい
if (x = 10) { // !!! 間違い !!! これは代入
printf("xは10と等しいはずだが...?\n");
}
printf("xの値: %d\n", x);

このコードの if (x = 10) は、x10 を代入し、その代入された値である 10 が条件式の結果となります。10 は非ゼロなので真と評価されます。したがって、条件に関わらず if のブロックは常に実行され、x の値は 10 に書き換わってしまいます。

正しい比較は if (x == 10) です。

このようなミスを防ぐためのテクニックとして、「定数を左辺に書く」という方法があります。

c
int x = 5;
// 意図: xが10と等しいか比較したい
if (10 == x) { // 定数を左辺に書く (Yoda conditions とも呼ばれる)
printf("xは10と等しい。\n");
}
// if (10 = x) { ... } // これはコンパイルエラーになるため、ミスに気づきやすい

10 = x という代入は、左辺が定数なのでコンパイルエラーになります。これにより、代入演算子を使ってしまうミスをコンパイル時に発見しやすくなります。すべての比較でこのスタイルを強制する必要はありませんが、特に ==!= を使う際には検討する価値があります。

6.4.2 浮動小数点数の比較

floatdouble といった浮動小数点数を ==!= で直接比較することは、避けるべきです。浮動小数点数はコンピュータの内部で厳密な値を表現できない場合があり、計算誤差によって期待通りに比較できないことがあります。

例えば、0.1 + 0.2 が厳密に 0.3 にならないことがあります。

“`c

include

int main() {
double a = 0.1;
double b = 0.2;
double c = a + b;

printf("a + b = %.17f\n", c); // 0.3にならないことがある

if (c == 0.3) { // 間違いやすい比較
    printf("a + b は 0.3 と等しい。\n");
} else {
    printf("a + b は 0.3 と等しくない。\n");
}

return 0;

}
“`

このコードを実行すると、環境によっては a + b は 0.3 と等しくない。 と表示されることがあります。これは計算誤差によるものです。

浮動小数点数が等しいかどうかを判定する場合は、両者の差の絶対値が、非常に小さな許容範囲(イプシロンと呼ばれることが多い)よりも小さいかどうかで判断します。

“`c

include

include // fabs関数を使うために必要

int main() {
double a = 0.1;
double b = 0.2;
double c = a + b;
double epsilon = 0.000000001; // 許容誤差 (必要に応じて調整)

// fabs(x - y) < epsilon で等価性を判定
if (fabs(c - 0.3) < epsilon) {
    printf("a + b はほぼ 0.3 と等しい。\n");
} else {
    printf("a + b は 0.3 から離れています。\n");
}

return 0;

}
“`

浮動小数点数の比較は少し高度な内容ですが、数値計算を含むプログラムでは重要な注意点です。

第7章:if-else の代わりに使える制御構造 (簡単な紹介)

本記事の主なテーマは if-else ですが、特定の状況では if-else よりも適した別の制御構造が存在します。ここではそれらを簡単に紹介します。

7.1 switch

switch 文は、一つの変数の値や式の評価結果が、複数の固定された値(caseラベル)のいずれかと一致するかどうかを判定し、対応する処理を実行する場合に使います。特に、整数型の変数の値によって多くの分岐がある場合に、if-else if チェーンよりもコードがすっきりと読みやすくなることがあります。

“`c

include

int main() {
int day_of_week = 3; // 1:日, 2:月, …, 7:土

switch (day_of_week) {
    case 1:
        printf("日曜です。\n");
        break; // breakがないと次のcaseにフォールスルーする
    case 2:
        printf("月曜です。\n");
        break;
    case 3:
        printf("火曜です。\n");
        break;
    case 4:
        printf("水曜です。\n");
        break;
    case 5:
        printf("木曜です。\n");
        break;
    case 6:
        printf("金曜です。\n");
        break;
    case 7:
        printf("土曜です。\n");
        break;
    default: // どのcaseとも一致しない場合
        printf("無効な曜日です。\n");
        break;
}

return 0;

}
“`

switch 文は、評価対象が単一の値であり、その値が離散的な複数の固定値(整数型、文字型、列挙型など)のいずれかであるかを判定する場合に有効です。範囲判定(例: score >= 80 && score < 90)には switch 文は直接使えません。

7.2 条件演算子 (? :)

条件演算子は、if-else 文で単純な値の代入や決定を行う場合に使用できる三項演算子です。

構文は 条件式 ? 式1 : 式2; です。

  • 条件式 が真(非ゼロ)なら、式1 が評価され、その値が式全体の値となります。
  • 条件式 が偽(ゼロ)なら、式2 が評価され、その値が式全体の値となります。

“`c

include

int main() {
int a = 10;
int b = 20;
int max;

// if-else文の場合
if (a > b) {
    max = a;
} else {
    max = b;
}
printf("最大値 (if-else): %d\n", max);

// 条件演算子の場合
max = (a > b) ? a : b;
printf("最大値 (?:): %d\n", max);

// 条件演算子をprintf内で使う例
printf("aとbの最大値は %d です。\n", (a > b) ? a : b);


return 0;

}
“`

条件演算子は、このように値を決定するシンプルな if-else を1行で記述できるため、コードを簡潔にできます。ただし、複雑な処理や複数の文を実行する場合には適しません。無理に条件演算子を使うと可読性が損なわれる可能性があるため、使いどころを判断することが重要です。

第8章:まとめと学習のステップ

8.1 本記事で学んだこと

本記事では、C言語における条件分岐の最も基本的な要素である if 文、if-else 文、そしてそれらを組み合わせた if-else if-else 文、さらにはネストについて詳細に解説しました。

  • プログラムが特定の状況に応じて異なる動作をするために、条件分岐が必要であることを理解しました。
  • if (条件式) という構文で、条件が真の場合に特定のブロックを実行する方法を学びました。
  • 条件式における「真(非ゼロ)」と「偽(ゼロ)」の評価方法を理解しました。
  • if-else 文を使って、条件が真の場合と偽の場合で異なる処理を実行する方法を学びました。
  • if-else if-else 文を使って、複数の条件を上から順番にチェックし、最初に真となった条件に対応する処理を実行する方法、そして最後の else が「どの条件にも一致しない場合」の処理として機能することを学びました。
  • if-else if チェーンでは、条件式の順序が結果に影響を与えるため重要であることを理解しました。
  • 条件分岐の中にさらに条件分岐を記述する「ネスト」の概念と、それを使ったより複雑な判断の表現方法を学びました。
  • ネストされたコードの可読性を高めるためのインデントの重要性、およびネストを深くしすぎることの弊害について知りました。
  • 波括弧 {} を常に使うことで、「ぶら下がりelse問題」などの一般的な落とし穴を避けることができると学びました。
  • 複数の条件を組み合わせるための論理演算子 (&&, ||, !) の使い方と、論理AND/ORにおける短絡評価の仕組みを理解しました。
  • 条件式を記述する際の注意点として、代入演算子 = と等価演算子 == の違い、および浮動小数点数の比較に関する落とし穴について学びました。
  • 最後に、特定のケースで if-else の代替となりうる switch 文と条件演算子 (? :) について簡単に紹介しました。

条件分岐は、あらゆるプログラムの基本的な構成要素です。ユーザーからの入力に応じた処理、エラーの検出とハンドリング、複雑なビジネスロジックの実装など、プログラムが「賢く」振る舞うためには条件分岐が不可欠です。

8.2 さらなる学習のために

本記事で解説した内容をしっかりと理解し、実際に手を動かしてコードを書いてみることが最も重要です。

  • 様々な条件式を試す: 比較演算子、等価演算子、論理演算子を組み合わせて、様々な条件式を作成し、その結果がどうなるかを確認してみましょう。
  • 例題を解く: 本記事の例(成績判定、偶奇判定など)を自分でゼロから書いてみたり、少し条件を変えて挑戦してみましょう。
  • 練習問題を探す: オンラインのプログラミング学習サイトや教科書には、条件分岐に関する多くの練習問題があります。積極的に挑戦し、様々なパターンの条件分岐の記述に慣れましょう。
  • 既存のコードを読む: オープンソースプロジェクトなど、他の人が書いたC言語のコードを読んで、実際のプログラムで条件分岐がどのように使われているかを学びましょう。
  • デバッガを使う: 条件式が期待通りに評価されているか確認するために、デバッガを使ってプログラムの実行をステップ実行してみましょう。変数の値や条件式の評価結果を追うことで、理解が深まります。
  • switch 文や条件演算子も学ぶ: if-else をマスターしたら、これらの代替手段についても詳しく学び、それぞれの利点・欠点や適切な使い分けを理解しましょう。

条件分岐はプログラミングの基礎の基礎です。ここをしっかりと身につけることが、C言語を使った今後のより高度な学習や、実用的なプログラム開発の礎となります。焦らず、着実に理解を深めていってください。

これで、C言語における if-else による条件分岐の基本に関する詳細な記事は終わりです。


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール