C言語では、反復処理を実現するための三つの基本的なループ構文が提供されています:`while`、`for`、および`do-while`。これらはそれぞれ異なる実行タイミングと構文的特徴を持ち、状況に応じて適切に選択することで、コードの可読性・保守性・安全性を高めることができます。本稿では、各ループの動作原理、実際の使用例、および制御文(`break`/`continue`)との連携について、実装コードを交えながら体系的に解説します。
1. whileループ:条件先行型反復
`while`は「条件式が真である間、ブロックを繰り返し実行」する構文です。構文はシンプルで、条件評価がループ本体の実行前に必ず行われます。
実行フロー
- 条件式を評価する(非ゼロ → 真、ゼロ → 偽)
- 真ならループ本体を実行し、その後再び条件式へ戻る
- 偽ならループを抜けて次の文へ進む
例: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~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`の活用)が可能な場合はそちらを優先すべきです。