ループの途中終了を行うbreakや、その周の処理だけを飛ばすcontinueは、探索や入力処理などで効率と可読性を高める重要な構文です。
本記事ではfor/while/do-whileでの動作の違いを押さえつつ、具体例とともに安全で読みやすい使い方を解説します。
breakの基本
動作の概要
breakは「いちばん内側のループ」または「switch文」を即座に抜ける制御文です。
実行されると、その場でブロックを離脱し、ループやswitchの直後の文に制御が移ります。
for文では、breakに到達した反復では増分式は実行されません。
while/do-whileでも、残りの本体は実行されず、次の条件評価にも進まず直後へ移動します。
主な用途
- 条件を満たした時点で探索を終了して無駄な反復を避ける用途に適しています。
- エラーや異常値を検出したら早期に処理を打ち切る「早期脱出」によく使います。
- 無限ループ(例: while (1))の終了条件として、入力終端や外部イベントを契機にbreakするのが一般的です。
使いどころの見極め
breakは「もうこれ以上ループを回しても意味がない」と判断できる瞬間にのみ使うと、意図が明確になり保守性が上がります。
continueの基本
動作の概要
continueは「現在の反復の残りの処理をスキップして次の反復に進む」ための制御文です。
breakのようにループ全体を抜けるのではなく、その周だけを飛ばします。
ループ種類ごとのジャンプ先
次の通り、continue後にどこへ制御が移るかはループの種類で異なります。
- for (初期化; 条件; 増分):
- 増分式へ移動し、その後に条件判定へ進みます。
- while (条件):
- 直ちに条件判定へ戻ります。
- do { … } while (条件);
- doブロックの末尾から条件判定へジャンプします(最低1回は本体が実行済み)。
主な用途
- 入力や配列走査で「不要なケース(例: 空白や不正値)」を早めにスキップします。
- バリデーションのやり直しなど、正常系の処理を読みやすく保つために前提を満たさないものを排除します。
for文での例
例1: breakで探索を途中終了
// break:最初に見つかった値で探索を終了
int arr[] = {3, 8, 5, 8, 2};
int target = 8;
int index = -1;
for (int i = 0; i < 5; i++) {
if (arr[i] == target) {
index = i;
break; // ここでforを抜ける
}
}
例2: continueで不要な反復をスキップ
// continue:偶数だけを処理
for (int i = 0; i < 10; i++) {
if (i % 2 != 0) continue; // 奇数はスキップ
// 偶数の処理
}
動くサンプル(出力あり)
説明を補うため、上記の2点をまとめて動作確認します。
#include <stdio.h>
int main(void) {
// 1) breakで最初の一致を見つけたら探索終了
int arr[] = {3, 8, 5, 8, 2};
int target = 8;
int index = -1;
for (int i = 0; i < 5; i++) {
if (arr[i] == target) {
index = i;
break; // 最初の8を見つけた時点でforを抜ける
}
}
if (index >= 0) {
printf("target %d は index %d に見つかりました\n", target, index);
} else {
printf("target %d は見つかりませんでした\n", target);
}
// 2) continueで偶数だけを表示
printf("偶数だけを出力: ");
for (int i = 0; i < 10; i++) {
if (i % 2 != 0) continue; // 奇数はスキップ
printf("%d ", i);
}
printf("\n");
return 0;
}
target 8 は index 1 に見つかりました
偶数だけを出力: 0 2 4 6 8
while文での例
例1: breakでループ終了(’q’で終了)
// break:'q' で終了
int ch;
while ((ch = getchar()) != EOF) {
if (ch == 'q') break; // ループ終了
// 入力処理
}
例2: continueで不要文字を無視(空白をスキップ)
// continue:空白は無視
int ch;
while ((ch = getchar()) != EOF) {
if (ch == ' ') continue; // 空白はスキップ
// 非空白の処理
}
標準入力を使わず動きを再現するデモ(出力あり)
処理の流れを見やすくするため、文字列を擬似入力として扱う例です。
#include <stdio.h>
int main(void) {
const char *s = "a b q c"; // 空白と 'q' を含む文字列
printf("breakデモ('q'で停止): ");
for (int i = 0; s[i] != '\0'; i++) {
char ch = s[i];
if (ch == 'q') {
printf("[q検出で停止]");
break; // 'q'を見つけたら終了
}
if (ch != ' ') {
printf("%c", ch); // 非空白のみ出力
}
}
printf("\n");
printf("continueデモ(空白をスキップ): ");
for (int i = 0; s[i] != '\0'; i++) {
char ch = s[i];
if (ch == ' ') continue; // 空白はスキップ
printf("%c", ch);
}
printf("\n");
return 0;
}
breakデモ('q'で停止): ab[q検出で停止]
continueデモ(空白をスキップ): abqc
do-whileでの注意
実行順序の前提
- do-whileは「必ず本体を1回実行してから」条件判定します。よって、初回は条件に関係なく実行されます。
- continueは「doブロック末尾まで一気に飛び」、そのままwhileの条件評価に進みます。
動作の確認コード(出力あり)
#include <stdio.h>
int main(void) {
int i = 0;
do {
i++;
if (i % 2 == 0) {
// 偶数のときは末尾までスキップして条件判定へ
continue;
}
printf("奇数だけ表示: i=%d\n", i);
} while (i < 5);
return 0;
}
奇数だけ表示: i=1
奇数だけ表示: i=3
奇数だけ表示: i=5
ネスト時の挙動
基本原則
break/continueは「最も内側のループ」にのみ作用します。
多重ループをまとめて抜けたい場合、Cにはラベル付きbreakがないため、設計で工夫が必要です。
例: 内側だけを抜けるbreak
#include <stdio.h>
int main(void) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (j == 1) break; // 内側のforだけを抜ける
printf("i=%d j=%d\n", i, j);
}
printf("内側ループを抜けた後: i=%d\n", i);
}
return 0;
}
i=0 j=0
内側ループを抜けた後: i=0
i=1 j=0
内側ループを抜けた後: i=1
i=2 j=0
内側ループを抜けた後: i=2
多重ループを一気に抜けたいとき
方法1: フラグで外側も抜ける
#include <stdio.h>
#include <stdbool.h>
int main(void) {
bool done = false;
for (int i = 0; i < 3 && !done; i++) {
for (int j = 0; j < 3; j++) {
if (i == 1 && j == 2) { // ある条件
done = true; // フラグON
break; // まず内側を抜ける
}
printf("i=%d j=%d\n", i, j);
}
// 外側ループは条件部の!doneで抜ける
}
return 0;
}
方法2: 関数化してreturnで抜ける
深いネストになる処理は関数に分割し、見つかった時点でreturnするのが読みやすい設計です。
可読性とテスト容易性が向上します。
switchとの関係
breakはswitchだけを抜ける
switch文の中でbreakすると、switch文だけを抜け、ループは継続します。
switch内のcontinueは外側のループへ作用
ループ内のswitchでcontinueを書くと、「switchではなくループの次反復へ」進みます。
動作の比較(出力あり)
#include <stdio.h>
int main(void) {
for (int i = 0; i < 4; i++) {
printf("---- i=%d ----\n", i);
switch (i % 2) {
case 0:
printf("case 0: switchのbreak -> ループは継続\n");
break; // switchのみを抜ける
case 1:
printf("case 1: switch内のcontinue -> ループの次反復へ\n");
continue; // ループの次反復へ(下のprintfは実行されない)
}
printf("switchの後に到達(i=%d)\n", i);
}
return 0;
}
---- i=0 ----
case 0: switchのbreak -> ループは継続
switchの後に到達(i=0)
---- i=1 ----
case 1: switch内のcontinue -> ループの次反復へ
---- i=2 ----
case 0: switchのbreak -> ループは継続
switchの後に到達(i=2)
---- i=3 ----
case 1: switch内のcontinue -> ループの次反復へ
よくある落とし穴とベストプラクティス
落とし穴1: continueの多用でフローが読みにくい
continueを何度も使うと、正規の処理がどこか分かりにくくなります。
条件の反転や早期return、ガード節を活用して読みやすくしましょう。
悪い例(分岐が多く読みにくい):
// 不要条件を次々にcontinueで捌くとフローが追いづらい
for (int i = 0; i < n; i++) {
if (!is_valid(a[i])) continue;
if (is_duplicate(a[i])) continue;
if (!has_permission(a[i])) continue;
// 本来の処理
process(a[i]);
}
改善例(ガード節をまとめて早期スキップ):
// 条件を一箇所に集約し、正常系を目立たせる
for (int i = 0; i < n; i++) {
if (!is_valid(a[i]) || is_duplicate(a[i]) || !has_permission(a[i])) {
continue; // スキップ条件を束ねる
}
process(a[i]); // 正常系が目立つ
}
落とし穴2: break/continueに依存しすぎる設計
複雑な状態管理をbreak/continueで切り抜けるより、関数分割や状態遷移の明示、早期returnを使った方がコードの意図が伝わりやすくなります。
落とし穴3: 無限ループに脱出経路がない
while (1)やfor(;;)を使うときは、必ずbreakに到達できる条件を1箇所以上用意します。
入力終端、タイムアウト、エラー検出など、確実に抜けられる道を設計しましょう。
例:
#include <stdio.h>
#include <stdbool.h>
int main(void) {
int ch;
bool timeout = false; // 例: 外部で更新されるタイムアウト
while (1) {
ch = getchar();
if (ch == EOF) break; // 入力終端で脱出
if (timeout) break; // タイムアウトで脱出
if (ch == 'q') break; // 終了コマンド
// 通常処理...
}
return 0;
}
まとめ
breakは「最も内側のループまたはswitch」を即座に終了し、直後へ制御を移します。continueは「その周の残り」をスキップして次の反復へ進みます。
for/while/do-whileでのcontinueのジャンプ先は異なります。forは増分式→条件、while/do-whileは条件判定へ向かう点を理解して使い分けます。
ネスト時は最内側にのみ作用すること、switch内のbreakはswitchだけを抜け、switch内のcontinueは外側ループに効くことに注意します。
可読性を損なう過剰なbreak/continueの使用は避け、必要な箇所に限定して意図を明確に書くことが、堅牢で理解しやすいループ制御への近道です。