回数を決めずに繰り返す
for ループは、何周必要かが分かっているか計算できるときの正しい道具です。しかし多くの仕事には回数が付いてきません。ファイルの全行を終わりまで読む。正しくなるまでパスワードを尋ね続ける。1に達するまで数を半分にし続ける。これらでの問いは「何回」ではなく「いつまで」であり、それこそ while が表現するものです。
誰も前もって「7」と計算していません。ループは条件が成り立つ間、ただ進み続けるだけです。
構文と、条件が先という規則
while (condition) {
// 本体 - 条件が非ゼロの間は実行される
}
条件は最初の周回を含め、毎回の周回の前に判定されます。帰結が3つあります。
- 条件が最初から偽のループは0回実行されます。
while (0) { ... }は本体を一度も実行しません。 - 条件が判定するものは何であれループの前に初期化されていなければ、ゴミの値を判定することになります。
- 本体の中の何かがいずれ条件を偽にしなければ、ループは終わりません。
if と同じくかっこは必須で、そのあとにセミコロンは付きません。その迷子のセミコロンは同じバグのループ版です。
/* BUG: 空文が本体になる - これは永久に回り続ける */
while (i < 10);
{
printf("%d\n", i);
i++;
}
i は決して増えないので、条件も決して変わりません。gcc -Wall は空の本体を指摘します。
Cの真偽の規則は if/else とまったく同じくここでも当てはまります。式が非ゼロの間ループは続き、ゼロになると止まります。
標準の形:初期化、判定、更新
正しい while ループのほとんどは、3か所に散った3つの部品を持っています。
これは for のヘッダーが抱える3つの部品と同じもので、ただ1行にまとめられていないだけです。だからこそ「for か while か?」への正直な答えはこうなります。3つの部品がすべて1つのカウンタについてのものなら、for はそれらを読み手が一緒に確認できる場所に置いてくれます。更新が長い本体の中に散らばっていたり、条件が数え上げ以外のものについてなら、while のほうが明快です。
部品3を忘れることがまさにwhileループのバグです。ループが止まらなくなったら、最初に確認すべきは、本体が本当に条件の読むものを変えているかどうかです。
番兵ループ
番兵(sentinel)とは「止まれ」を意味する特別な値です。ユーザーが 0 を入れるまで数を足していきましょう。
0 のあとの 99 には決して到達しません - 番兵がループを終わらせました。数の個数が前もって分からないことに注目してください。まさにそれがこれを while にする理由です。
C言語の文字列も同じように働きます。'\0' という番兵で終わるので、それを歩くのは教科書どおりの while です。
EOF まで入力を読む
実務で最もよくある while は、入力がなくなるまで読むものです。規則は、格納された値ではなく入力関数の戻り値を判定することです。
getchar は次の文字を返し、入力が尽きると定数 EOF を返します。
重要な細部が2つ。c が int なのは、EOF が多くのシステムで char に収まらない負の値だからです - char と宣言すると比較が決して成立しなくなりえます。そして代入が条件の中に入っています。(c = getchar()) != EOF は1文字を読み、格納し、比較するのを一歩で行います。内側のかっこは必須です。!= が = より強く結びつくので、なければ比較の結果が c に格納されてしまいます。ここは、条件の中の代入がバグではなく慣用的である数少ない場所の1つです。
同じ形は scanf でも働きます。その戻り値は変換に成功した項目の個数です。
int n;
while (scanf("%d", &n) == 1) {
/* n は妥当な数を保持している */
}
このループはファイル終端で終わり、かつ数でない最初の入力でも終わります。たいていそれが望ましい挙動です。
書いてはいけないのは while (!feof(f)) です。feof は読み取りがすでに失敗したかどうかを報告するので、最後の項目の上にいる時点ではまだ偽であり、ループの本体がもう一度走り、その読み取りが失敗し - 古いデータを処理してしまいます。読み取り自体を判定しましょう。
while (1) と break
ときには、抜ける条件が本体の途中でしか分からないことがあります - 読んだあと、処理する前とか。慣用的な答えは、明示的な脱出を伴う意図的な無限ループです。
while (1) は常に真なので、抜ける唯一の道は break(あるいは return、exit)です。同じ目的に for (;;) が使われるのも見かけます。どちらも同じものにコンパイルされ、どちらをコードベースが好むかは流儀の問題です。
抜けるための判定が本当に本体の途中に属するときは、この形を使いましょう。判定が自然に先頭に収まるときは、読み手に見えるよう条件に置きましょう - 唯一の break が最初の文である while (1) は、姿を変えただけの条件です。
for と while の相互変換
どの for ループも while ループとして書けますし、その逆もできます。対応は機械的です。
/* for */ /* 等価な while */
for (init; cond; update) { init;
body; while (cond) {
} body;
update;
}
だからこの2つは同じものを表示します。
この等価性が漏れる唯一の場所が continue です。for ループの中の continue はヘッダーの更新へ飛ぶので、カウンタは進みます。while 版では continue は条件へ直接戻り、本体の末尾の i++ を飛ばします - 即座に無限ループです。
/* BUG: continue が i++ を飛ばすので、最初の偶数で永久に回り続ける */
int i = 0;
while (i < 10) {
if (i % 2 == 0) {
continue;
}
printf("%d ", i);
i++;
}
while ループで continue を使うなら、更新がその前に行われるようにするか、ヘッダーがそれを保証してくれる for を使いましょう。
よくある落とし穴
- 条件を更新するものがない。 本体は条件が読むものを変えなければなりません。プログラムが止まらなくなったら、まずここを見ましょう。
- ヘッダーのあとのセミコロン。
while (cond);は空文を本体にします。 - 一度も初期化されていない変数を判定する。 条件は本体より前に走るので、それが読む値はすでに存在していなければなりません。
getcharにcharを使う。EOFはcharに収まりません。比較が決して真にならないか、正当なバイトがEOFと誤解されます。while (!feof(f))。 1回多く回ります。読み取りを判定しましょう。- 本体が少なくとも1回は走ると思い込む。 走りません。保証された1周が必要なとき - 選択を確認する前にメニューを表示するなど - それは do-while ループです。
よくある質問
C言語でwhileループはどう書きますか?
while (condition) { body } です。条件は毎回の周回の前に判定されるので、本体は条件が非ゼロの間だけ実行され、最初から偽ならまったく実行されません。本体の中の何かがいずれ条件を変えなければ、ループは終わりません。
C言語の for と while の違いは何ですか?
能力は同等で、どの for も while に書き換えられます。カウンタとその上限と刻みが1つのヘッダーにまとまるときは for を、停止条件が数え上げでないとき - ファイル終端まで読む、成功するまで再試行する、フラグが変わるまで回る - は while を使いましょう。
C言語でEOFまで入力を読むには?
読み取った値ではなく、入力関数の戻り値を判定します。while (scanf("%d", &n) == 1) { ... } はファイル終端か最初の数字でない入力で止まり、while ((c = getchar()) != EOF) { ... } は1文字ずつ読みます。while (!feof(f)) でループしてはいけません - 末尾を越えて1回余分に回ります。
なぜ私のC言語のwhileループは無限になるのですか?
たいていは、本体の中に条件が判定している値を変えるものがないか、ヘッダーのあとの迷子のセミコロン(while (i < 10);)が空文を本体にしてしまったからです。どちらも問題なくコンパイルされるので、本体が本当に条件の変数を更新しているか確認しましょう。