C言語における制御フロー:while、for、do-whileの実践的比較と応用

C言語では、反復処理を実現するための三つの基本的なループ構文が提供されています:`while`、`for`、および`do-while`。これらはそれぞれ異なる実行タイミングと構文的特徴を持ち、状況に応じて適切に選択することで、コードの可読性・保守性・安全性を高めることができます。本稿では、各ループの動作原理、実際の使用例、および制御文(`break`/`continue`)との連携について、実装コードを交えながら体系的に解説します。

1. whileループ:条件先行型反復

`while`は「条件式が真である間、ブロックを繰り返し実行」する構文です。構文はシンプルで、条件評価がループ本体の実行前に必ず行われます。

実行フロー

  1. 条件式を評価する(非ゼロ → 真、ゼロ → 偽)
  2. 真ならループ本体を実行し、その後再び条件式へ戻る
  3. 偽ならループを抜けて次の文へ進む

例:1から10までの整数を出力

#include <stdio.h>
int main() {
    int counter = 1;
    while (counter <= 10) {
        printf("%d ", counter);
        counter++;
    }
    printf("\n");
    return 0;
}

この例では、初期化・条件判定・更新の3要素が明示的に分離しており、特に複雑なループ制御では意図しない変更によるバグ発生リスクがあります。

2. forループ:統合型反復構文

`for`は、初期化・継続条件・更新式を1行に集約できるため、ループの構造が視覚的に明確になります。これは大規模な反復処理やネストされたループにおいて特に有効です。

構文形式

for (初期化式; 条件式; 更新式) {
    // ループ本体
}

動作順序

  1. 初期化式(1回のみ実行)
  2. 条件式を評価 → 偽なら終了
  3. 真なら本体を実行
  4. 更新式を実行 → 再び条件式へ

同様の出力例(1~10)

#include <stdio.h>
int main() {
    for (int num = 1; num <= 10; num++) {
        printf("%d ", num);
    }
    printf("\n");
    return 0;
}

補足:各節の省略可能性

初期化・条件・更新のいずれも省略可能ですが、条件式を省略すると無限ループ(`for(;;)`)となります。意図的な無限ループには`break`や`return`で脱出する必要があります。

3. do-whileループ:最低1回保証型反復

`do-while`は「ループ本体を少なくとも1回実行した後、条件を評価する」構文です。条件が初回から偽でも、本体は1度は実行されます。

構文とフロー

do {
    // 実行される本体
} while (条件式);

→ 本体実行 → 条件評価 → 真なら再実行、偽なら終了

例:ユーザー入力のバリデーション

#include <stdio.h>
int main() {
    int input;
    do {
        printf("1〜10の整数を入力してください: ");
        scanf("%d", &input);
    } while (input < 1 || input > 10);
    printf("有効な入力: %d\n", input);
    return 0;
}

4. 制御文:breakとcontinueの振る舞い

`break`は直近の囲むループ(または`switch`)を即座に終了させ、`continue`は現在のイテレーションをスキップして次回の条件評価へ移ります。両者の挙動はループ種別によって若干異なります。

whileでの挙動

// break:i==5で即時脱出 → 出力: 1 2 3 4
int i = 1;
while (i <= 10) {
    if (i == 5) break;
    printf("%d ", i);
    i++;
}

// continue:i==5でprintfをスキップ → 出力: 1 2 3 4 6 7 8 9 10
i = 1;
while (i <= 10) {
    if (i == 5) {
        i++;  // ※必須:更新をcontinueより前に配置
        continue;
    }
    printf("%d ", i);
    i++;
}

forでの挙動(更新式の自動実行に注意)

// continue時に更新式(i++)は自動実行されるため、
// i==5でprintfを飛ばしても、次はi==6になる
for (int i = 1; i <= 10; i++) {
    if (i == 5) continue;
    printf("%d ", i);  // → 1 2 3 4 6 7 8 9 10
}

5. ループのネスト:素数判定の実装

複数のループを組み合わせることで、より複雑なアルゴリズムを表現できます。以下は100〜200の範囲内で素数を検出する例です。

#include <stdio.h>
int main() {
    for (int candidate = 100; candidate <= 200; candidate++) {
        int is_prime = 1;
        // candidateが2〜√candidateまで割り切れないか確認(最適化)
        for (int divisor = 2; divisor * divisor <= candidate; divisor++) {
            if (candidate % divisor == 0) {
                is_prime = 0;
                break;
            }
        }
        if (is_prime) {
            printf("%d ", candidate);
        }
    }
    printf("\n");
    return 0;
}

内側の`for`ループは、候補値が合成数かどうかを高速に判定するために、`divisor * divisor <= candidate`という平方根上限を利用しています。

6. goto文:限定的な用途での脱出支援

`goto`は関数内の任意のラベルへ無条件ジャンプする構文です。多層ネストからの一括脱出など、例外処理やエラー回復の場面で有用です。

#include <stdio.h>
int main() {
    for (int a = 0; a < 5; a++) {
        for (int b = 0; b < 5; b++) {
            for (int c = 0; c < 5; c++) {
                if (a + b + c == 12) {
                    goto cleanup;  // 全ループを一括終了
                }
            }
        }
    }

cleanup:
    printf("条件成立: a=%d, b=%d, c=%d\n", a, b, c);
    return 0;
}

ただし、過度な`goto`使用は制御フローを難解にするため、代替手段(関数分割・フラグ管理・`break`の活用)が可能な場合はそちらを優先すべきです。

タグ: c-language control-flow loop-structures programming-fundamentals

5月16日 19:24 投稿