C言語のwhile文は、条件が真の間処理を繰り返す最も基本的なループ構文です。
この記事では、while文の基本構文から、breakやcontinueによる制御、典型パターン、つまずきやすいエラーの回避まで、入門者でも手を動かしながら学べるように丁寧に解説します。
while文: 基本と使い方
基本構文と実行の流れ
while
は「条件が真(true)の間だけ処理を繰り返す」前判定ループです。
実行の流れは、条件式の評価→本体の実行→更新→再び条件式の評価、という順序になります。
条件が最初から偽(false)であれば、一度も本体を実行しません。
サンプル: 0から2まで数える
以下は、制御変数を更新しながら3回だけ繰り返す最小例です。
#include <stdio.h>
int main(void) {
int i = 0; // 制御変数の初期化
while (i < 3) { // 条件式の評価(真の間だけ本体が実行される)
printf("i=%d\n", i); // 本体
i++; // 制御変数の更新(忘れると無限ループ)
}
printf("done\n");
return 0;
}
i=0
i=1
i=2
done
実行の流れの要点
- ループの入口で必ず条件式が評価されます。
- 条件式が偽になった瞬間にループは終了します。
- 制御変数の初期化と更新は明示的に書きます(forのようなヘッダ内一体型ではありません)。
条件式の書き方(==, !=, &&, ||)
条件式には、比較演算子や論理演算子を組み合わせて表現します。
数値の大小比較や一致・不一致に加え、複数条件のANDやORを使って繰り返しの継続条件を作ります。
主な演算子の一覧と例
演算子 | 意味 | 例 | 真となる条件 |
---|---|---|---|
== | 等しい | a == b | aとbが等しい |
!= | 等しくない | a != b | aとbが異なる |
<, <= | より小さい, 以下 | a < b | aがbより小さい |
>, >= | より大きい, 以上 | a > b | aがbより大きい |
&& | 論理積(AND) | x < 10 && y > 0 | 両方が真 |
|| | 論理和(OR) | x == 0 || y == 0 | どちらかが真 |
! | 否定(NOT) | !(a == b) | a==bが偽 |
サンプル: 論理演算子を含む複合条件
次のプログラムは、比較と論理演算を確認しつつ、x<3 && y<2
が満たされる間だけループします。
#include <stdio.h>
int main(void) {
int a = 3, b = 5;
// 比較や論理演算の評価結果は、Cでは0か1として扱われます。
printf("(a==b)=%d\n", a == b);
printf("(a!=b)=%d\n", a != b);
printf("(a<b || b<0)=%d\n", (a < b || b < 0));
printf("(a==3 && b>0)=%d\n", (a == 3 && b > 0));
int x = 0, y = 0;
while (x < 3 && y < 2) { // 複合条件: 両方満たす間だけ継続
printf("x=%d y=%d\n", x, y);
x++; // 更新1
if (x % 2 == 0) y++; // 更新2(条件付き更新の例)
}
return 0;
}
(a==b)=0
(a!=b)=1
(a<b || b<0)=1
(a==3 && b>0)=1
x=0 y=0
x=1 y=0
x=2 y=1
制御変数の初期化と更新
while
では、ループの回数を決める制御変数を自分で初期化し、ループ本体の中で適切に更新する必要があります。
初期化の漏れや更新の漏れは、意図せぬ無限ループや未定義の挙動につながります。
誤り例と正しい例
以下は更新を忘れてしまう典型的な誤りです。
/* 誤り例: 更新忘れによる無限ループ
#include <stdio.h>
int main(void) {
int i = 0;
while (i < 3) {
printf("%d\n", i);
// i++ を忘れているため i が変化せず、条件がいつまでも真のまま
}
return 0;
}
*/
修正版では、初期化→条件→更新の3点がそろっています。
#include <stdio.h>
int main(void) {
int i = 0; // 初期化
while (i < 3) { // 条件
printf("Step %d\n", i); // 本体
i++; // 更新(重要)
}
printf("loop end\n");
return 0;
}
Step 0
Step 1
Step 2
loop end
do-whileは別記事で解説
do { ... } while (条件);
という後判定ループもありますが、こちらは少なくとも1回は本体を実行するのが特徴です。
本記事では触れません。
使い分けや注意点は別記事で詳しく解説します。
breakとcontinueの基本
breakの使い方と終了タイミング
break
は現在のループをただちに抜けます。
入れ子(ネスト)がある場合は、宣言された最も内側のループだけを終了します。
#include <stdio.h>
int main(void) {
int i = 0;
while (1) { // 無限ループ
if (i == 5) { // 条件を満たしたらループを抜ける
break; // ここで while を終了
}
printf("%d ", i);
i++;
}
printf("\nloop finished at i=%d\n", i);
return 0;
}
0 1 2 3 4
loop finished at i=5
continueの使い方とスキップ
continue
は現在の反復の残りをスキップして、ただちに次の反復の条件評価に進みます。
#include <stdio.h>
int main(void) {
int i = 0;
while (i < 10) {
i++;
if (i % 2 == 0) {
continue; // 偶数はスキップ
}
printf("%d ", i); // 奇数だけ出力される
}
printf("\n");
return 0;
}
1 3 5 7 9
ネスト時のbreak/continueの挙動
入れ子ループではbreak
もcontinue
も「今いるループ」にだけ作用します。
外側のループには影響しません。
#include <stdio.h>
int main(void) {
int outer, inner;
printf("== case A: continue in inner ==\n");
outer = 1;
while (outer <= 2) {
inner = 1;
printf("outer %d start\n", outer);
while (inner <= 3) {
if (inner == 2) {
printf(" inner=%d -> continue\n", inner);
inner++; // 増やしてからスキップ
continue; // 内側ループ内の次反復へ
}
printf(" inner=%d\n", inner);
inner++;
}
printf("outer %d end\n", outer);
outer++;
}
printf("\n== case B: break in inner ==\n");
outer = 1;
while (outer <= 2) {
inner = 1;
printf("outer %d start\n", outer);
while (inner <= 3) {
if (inner == 2) {
printf(" inner=%d -> break\n", inner);
break; // 内側ループだけを抜ける
}
printf(" inner=%d\n", inner);
inner++;
}
printf("outer %d end (after inner break)\n", outer);
outer++;
}
return 0;
}
== case A: continue in inner ==
outer 1 start
inner=1
inner=2 -> continue
inner=3
outer 1 end
outer 2 start
inner=1
inner=2 -> continue
inner=3
outer 2 end
== case B: break in inner ==
outer 1 start
inner=1
inner=2 -> break
outer 1 end (after inner break)
outer 2 start
inner=1
inner=2 -> break
outer 2 end (after inner break)
while文の典型パターン
カウンタで回す(0からn-1)
最も基本的なパターンで、配列の先頭から末尾まで処理する時にも頻出です。
#include <stdio.h>
int main(void) {
int n = 5; // 0 から n-1 まで
int i = 0;
while (i < n) {
printf("%d ", i);
i++; // 忘れずに更新
}
printf("\n");
return 0;
}
0 1 2 3 4
条件成立まで繰り返す(番兵値)
入力などを、特定の値(番兵値)が現れるまで処理し続けるパターンです。
#include <stdio.h>
int main(void) {
int sum = 0;
int x;
while (1) { // 条件は常に真、内部で break
printf("値を入力(-1で終了): ");
if (scanf("%d", &x) != 1) { // 読み取り失敗なら終了
printf("入力が不正です。終了します。\n");
return 1;
}
if (x == -1) { // 番兵値
break; // ループを抜ける
}
sum += x;
}
printf("合計=%d\n", sum);
return 0;
}
値を入力(-1で終了): 10
値を入力(-1で終了): 20
値を入力(-1で終了): -1
合計=30
入力をEOFまで処理(読み込みループ)
入力の終わり(EOF)まで処理するのはwhile (scanf(...) == 1)
の定番です。
戻り値で成功回数を判定できるため、安全に繰り返せます。
#include <stdio.h>
int main(void) {
int x, sum = 0, count = 0;
printf("整数をスペースまたは改行で入力してください。EOFで終了します。\n");
// 整数を読めた間(戻り値が1の間)だけループ
while (scanf("%d", &x) == 1) {
sum += x;
count++;
}
printf("読み込んだ個数=%d, 合計=%d\n", count, sum);
return 0;
}
整数をスペースまたは改行で入力してください。EOFで終了します。
24
12
10
e
読み込んだ個数=3, 合計=46
この例では、端末で「3 4 5」を入力し、その後EOFを送って終了しています。
EOFの操作は環境により異なります。
OS | EOFの操作方法 |
---|---|
macOS/Linux | Ctrl+D |
Windows(CMD/PowerShell) | Ctrl+Z を押してから Enter |
配列/文字列の走査(インデックス/ポインタ)
配列はインデックスで、文字列はポインタで走査するとCらしい書き方になります。
#include <stdio.h>
int main(void) {
// 配列をインデックスで走査
int arr[] = {2, 4, 6, 8};
int n = (int)(sizeof(arr) / sizeof(arr[0]));
int i = 0;
int sum = 0;
printf("配列の要素: ");
while (i < n) {
printf("%d ", arr[i]); // i番目の要素にアクセス
sum += arr[i];
i++;
}
printf("\n合計=%d\n", sum);
// 文字列をポインタで走査
char s[] = "Hello";
char *p = s; // 配列先頭へのポインタ
int len = 0;
printf("文字列: ");
while (*p != '#include <stdio.h>
int main(void) {
// 配列をインデックスで走査
int arr[] = {2, 4, 6, 8};
int n = (int)(sizeof(arr) / sizeof(arr[0]));
int i = 0;
int sum = 0;
printf("配列の要素: ");
while (i < n) {
printf("%d ", arr[i]); // i番目の要素にアクセス
sum += arr[i];
i++;
}
printf("\n合計=%d\n", sum);
// 文字列をポインタで走査
char s[] = "Hello";
char *p = s; // 配列先頭へのポインタ
int len = 0;
printf("文字列: ");
while (*p != '\0') { // ヌル終端まで1文字ずつ
putchar(*p);
p++; // 次の文字へ
len++;
}
printf("\n長さ=%d\n", len);
return 0;
}
') { // ヌル終端まで1文字ずつ
putchar(*p);
p++; // 次の文字へ
len++;
}
printf("\n長さ=%d\n", len);
return 0;
}
配列の要素: 2 4 6 8
合計=20
文字列: Hello
長さ=5
よくあるエラーと対策
末尾セミコロンで空ループになる
while (条件);
のように末尾にセミコロンを置くと、何もしない空ループになり、その後のブロックはループ本体ではなくなります。
特にi
の更新が本体側にある場合、条件がいつまでも変わらず無限ループの原因になります。
/* 誤り例: 末尾セミコロンにより空ループ
#include <stdio.h>
int main(void) {
int i = 0, n = 3;
while (i < n); { // ← ここが空ループになる(本体は次のブロックではない)
printf("i=%d\n", i); // 実際にはループ外の単なるブロック
i++; // ここは実行されない可能性が高く、手前が無限ループに
}
return 0;
}
*/
正しくは次のようにセミコロンを付けず、波括弧のブロックをループ本体にします。
#include <stdio.h>
int main(void) {
int i = 0, n = 3;
while (i < n) {
printf("i=%d\n", i);
i++;
}
return 0;
}
i=0
i=1
i=2
更新忘れで無限ループになる
更新を忘れると条件がいつまでも真のままになり、プログラムが停止しません。
for
と違い、while
では更新を書く場所が本体の中に散らばりがちなので注意が必要です。
/* 誤り例: 更新を忘れている
#include <stdio.h>
int main(void) {
int i = 0;
while (i < 3) {
printf("%d\n", i);
// i++; を忘れている
}
return 0;
}
*/
修正は単純で、意図したタイミングで確実に更新します。
#include <stdio.h>
int main(void) {
int i = 0;
while (i < 3) {
printf("%d\n", i);
i++; // ここで更新
}
return 0;
}
0
1
2
浮動小数の比較は誤差に注意
浮動小数では==
による直接比較が成り立たないことがあります。
例えば0.1
を10回足しても、2進小数の丸め誤差で1.0
にならない場合があります。
そのため、while (x != 1.0)
のような条件は危険です。
/* 誤り例: 浮動小数の直接比較は危険
#include <stdio.h>
int main(void) {
double x = 0.0;
while (x != 1.0) { // いつまでも真のままになり得る
x += 0.1;
}
printf("x=%f\n", x);
return 0;
}
*/
正しくは、許容誤差(epsilon)を使って「ほぼ等しい」を判定します。
#include <stdio.h>
#include <math.h> // fabs のため
int main(void) {
double x = 0.0;
int steps = 0;
const double eps = 1e-9; // 許容誤差
while (fabs(x - 1.0) > eps) { // ずれが十分小さくなるまで繰り返す
x += 0.1;
steps++;
if (steps > 1000) { // 念のための安全弁
printf("安全のために強制終了します\n");
break;
}
}
printf("x=%.17f, steps=%d\n", x, steps);
return 0;
}
x=0.9999999999999999, steps=10
for文とwhile文の使い分け
for
は「回数が決まっている繰り返し」に向いており、初期化・条件・更新がヘッダにまとまって読みやすくなります。
while
は「いつ終わるかわからない繰り返し」や「条件を見ながら柔軟に抜ける処理」に向いています。
観点 | for | while |
---|---|---|
反復回数 | 事前に決まっている場合に向く | 未知で条件次第の場合に向く |
可読性 | 初期化・条件・更新が1行に集約 | 更新が本体内に分散することがある |
代表例 | インデックスで配列を回す | 入力をEOFや番兵まで読む |
同じ処理でも両者で書けます。
違いを比べてみましょう。
#include <stdio.h>
int main(void) {
int i;
// for で 0,1,2 を出力
printf("for: ");
for (i = 0; i < 3; i++) {
printf("%d ", i);
}
// while で同じことをする
printf("\nwhile: ");
i = 0; // 初期化
while (i < 3) { // 条件
printf("%d ", i);
i++; // 更新
}
printf("\n");
return 0;
}
for: 0 1 2
while: 0 1 2
使い分けの指針としては、回数が明確ならfor
、条件で終わりが決まるならwhile
、少なくとも1回実行が必要ならdo-while
と覚えると実務でも混乱しにくくなります。
まとめ
本記事では、C言語におけるwhile
文の基本構文、条件式の書き方、制御変数の初期化と更新、break
とcontinue
の使い方とネスト時の挙動、さらに典型パターン(カウンタ、番兵値、EOF読み込み、配列や文字列の走査)まで段階的に解説しました。
つまずきやすいポイントとして、末尾セミコロンによる空ループ、更新忘れの無限ループ、浮動小数の直接比較の危険性も具体例で確認しました。
実務では、可読性と安全性の観点から、条件式の明確化、更新位置の一貫性、scanf
の戻り値チェック、break
やcontinue
の適切な粒度を常に意識すると、バグを大幅に減らせます。
慣れてきたら同じ処理をfor
やdo-while
でも書いて比較し、状況に応じた最適なループ構文を選べるように練習してみてください。