今回は構造化プログラミングにおける 反復(繰り返し,ループ)について深く理解しよう.
Cでは3種類の反復文 for と while と do-while が用意されている. これらをうまく使い分けよう.
まず,while ループと do-while ループについて説明する. NS チャートを Fig.1 に示す.
どちらも条件式が成立している間だけ処理を繰り返すものであるが, 次のような違いがある:
一般形:
while ( 条件式 ) { 繰り返したい処理 }
一般形:
do { 繰り返したい処理 } while ( 条件式 );
条件反復を使ったプログラムの一例を List 1 に示す.
/* Not 100 : * 2 人以上で交互に,1 〜 3 個の間でカウントし, * 100 に達した人が負けという超つまらないゲーム * (簡易版のため,入力した数の範囲チェックをしてないので, * 負の数や 4 以上を入力できるなど,イカサマが可能) */ main() { int c = 0; int d; printf("*** Not 100 ***\n"); while (c < 100) { // または do { printf("[%3d] 1 〜 3 > ", c); scanf("%d", &d); c += d; } // または } while (c < 100); printf("*** Game Over ***\n"); }
このプログラムの場合, 繰り返し回数は1回以上なので, List 1 の while 文を do-while 文に書き換えても正常に動作するハズだ. 試してみよう.
練習: List 1 の while ループを do-while ループに書き替えよ.
for ループは,これまでに何度も使ってきた通り, 一定の回数だけ処理を繰り返すものである.
一般形:
for (カウンタの初期化; カウンタの条件式; カウント) { 繰り返したい処理 }
具体例:
int i; for (i = 0; i < 10; i++) { printf("i = %d\n", i); }
int i; for (i = 9; i >= 0; i--) { printf("i = %d\n", i); }
なお,カウンタ(counter)は回数をおぼえておくための変数であり, カウント(count)は回数を増加 or 減少させる処理である.
ところで, for は,while と同様に,前判定の条件反復の一種だ. 例として,n 回繰り返す場合の NS チャートについて, for 版と while 版の比較を Fig.2 に示しておく.
このように,for と while とは, 相互に書き換えが可能であり, 形式的にはほぼ同じものではあるが, 意味的には異なるものである. 処理の意図を明確にするために, 用途に応じて使い分けること.
ループの使い分け規則:以前作成した平均値計算プログラム averager.c では,計数反復の for ループでデータ入力を繰り返しており, データ入力の前に,データの個数を指定してやる必要があった. しかし,現実的には,入力作業が終わるまでは, 実際のデータの個数がわからない場合も多い.
そこで List 2 では,条件反復の while ループを使い, データの個数が不明なままでも実行できるように改良してみた.
main() { int total = 0; // 合計 int n = 0; // データの個数 int x; // データ int f = 1; // 反復フラグ printf("複数個の非負整数(最後に -1) > "); while (f) { // または while (f == 1) scanf("%d", &x); if (x < 0) { // 番兵の場合 f = 0; // フラグを下げ → 反復終了へ } else { // 有効データの場合 total += x; // 合計を計算 n++; // 個数をカウント } } printf("平均 = %f\n", (double)total/(double)n); }
なお,フラグ(flag;旗)は, 2値データ(Yes/No,有/無,真/偽,等)をおぼえておくための変数である. List 2 の反復フラグ f の働きは,次の通り:
while などの条件反復に対して, 常に成立している条件式を与えることによって, 無条件ループ(無限ループ)を実現できる.
前回の練習問題でも触れた通り, 条件式は,他の数式などと同様に,値をもっている:
条件式の値 | = 0 ...条件が不成立の場合
≠ 0 ...条件が成立している場合 |
このことを利用した無条件ループの一般形は次の通りである:
while (1) { 繰り返したい処理 }
これで,ループ内の処理は,永久に繰り返されることになる. (もちろん,実際には,ループ内のどこかに, 反復を止めるための処理を入れる必要がある.)
たとえば,ビデオゲームや GUI(graphical user interface)のプログラムでは, 無条件反復がメインループとして利用される. 「ゲームオーバになったら,とか,終了ボタンが押されたらループを終了する」 という処理がループ内に仕込まれている.
なお,教科書(p.73 等)では 無条件反復を for (;;) と記述している場合もあるが, マネしないこと. 無条件反復は反復回数が未知なので for よりも while の方がふさわしい.
ちなみに,次のようなループは無意味:
while (0) { 実行されない処理 }
実行例:
$ ./mul 非負整数 x, y > 3 4 3 * 4 = 12
実行例:
$ ./div 非負整数 x, y > 13 4 13 / 4 = 3
レポートには,ソースコードだけでなく,実行結果を記述すること. (提出の前に,さまざまな入力データを与えて動作テストを繰り返そう.)
余裕のある人は,反復を利用して, その他の計算(剰余,べき乗,階乗,等)についてもプログラミングしてみよう. また,負の値も使えるようにしてみるのもよいだろう.