05 月 15 日(金)1-2h

制御構造(2)反復

今回は構造化プログラミングにおける 反復(繰り返し,ループ)について深く理解しよう.

Cでは3種類の反復文 forwhiledo-while が用意されている. これらをうまく使い分けよう.

for 文については,すでに何度も使ってきた.

条件反復

まず,while ループと do-while ループについて説明する. NS チャートを Fig.1 に示す.

Fig.1. whiledo-while の NS チャート
条件式については, 前回if 文と共通.

どちらも条件式が成立している間だけ処理を繰り返すものであるが, 次のような違いがある:

条件反復を使ったプログラムの一例を List 1 に示す.

List 1. not100.c
/* 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");
}
このプログラムの実行中,数値以外を入力してしまうと, プログラムが暴走する可能性がある. 暴走してしまった場合, [Ctrl]+[C] キーを押し,強制終了しよう.

このプログラムの場合, 繰り返し回数は 1 回以上なので, List 1 の while 文を do-while 文に書き換えても正常に動作するハズだ. 試してみよう.


計数反復

for ループは,これまでに何度も使ってきた通り, 一定の回数だけ処理を繰り返すものである.

一般形:

for (カウンタの初期化; カウンタの条件式; カウント) {
	繰り返したい処理
}

具体例:

int i;

for (i = 0; i < 10; i++) {
	printf("i = %d\n", i);
}

なお,カウンタ(counter)は回数をおぼえておくための変数であり, カウント(count)は回数を増加 or 減少させる処理である.

実は,forwhile と同様に,前判定の条件反復の一種だ. 例として,n 回繰り返す場合の NS チャートについて, for 版と while 版の比較を Fig.2 に示しておく.

Fig.2. n 回の繰り返しの NS チャート

このように,forwhile との相互書き換えが可能だが, 次の通り,用途に応じて使い分けること:


終わるまで反復

以前作成した平均値計算プログラム averager.c では,計数反復の for ループでデータ入力を繰り返しており, データ入力の前に,データの個数を指定してやる必要があった. しかし,現実的には,入力作業が終わるまでは, 実際のデータの個数がわからない場合も多い.

事前に個数を数えられる場合でも,数えるのは面倒だし... 不便なのは悪いプログラムだ.

そこで List 2 では,条件反復の while ループを使い, データの個数が不明なままでも実行できるように改良してみた.

ただし,負の値を番兵 (データ終了を表わす特殊データ)として利用したので, 残念ながら,負のデータを入力することはできなくなってしまった. 番兵を使わない方法については,後日説明予定.
List 2. 平均値計算プログラムの改良版 averager2.c
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)のプログラムでは, 無条件反復がメインループとして利用される. 「ゲームオーバになったら,とか,終了ボタンが押されたらループを終了する」 という処理がループ内に仕込まれている.

なお,教科書では 無条件反復を for(;;) と記述している場合もあるが, マネしないこと. 無条件反復は反復回数が未知なので for よりも while の方がふさわしい.

ちなみに,次のようなループは無意味:

while (0) {
	実行されない処理
}
だが,デバッグ(間違い探し)の時など, 一時的に処理を無効化したい場合には, この技は便利かもしれない.

本日の課題

  1. 非負整数 x,y の積 x*y を計算するプログラム mul.cを作成せよ. ただし,乗算演算子 * を使わずに足し算の繰り返しによって積を求めること.
  2. ヒント: 小学校でも習った通り, x を y 回だけ足して行くと,その合計が積 x*y になる. 繰り返し回数 y が既知なので,反復にはfor 文を使う. 入力には,scanf("%d %d", &x, &y). あ,合計の初期値はゼロね.

    実行例:

    $ ./mul
    非負整数 x, y > 3 4
    3 * 4 = 12
    
  3. 非負整数 x,y の商 x/y を計算するプログラム div.cを作成せよ. ただし,除算演算子 / を使わずに引き算の繰り返しによって商を求めること.
  4. ヒント: 小学校でも習ったハズだが, x から y を引いて行き,負になる前に止める. 引いた回数が商 x/y だ. 反復には,while 文を使う. 回数がゼロの場合もありうるので,do-while ではダメだ. また,回数が未知なので,for でもダメ.

    実行例:

    $ ./div
    非負整数 x, y > 13 4
    13 / 4 = 3
    

レポートには,ソースコードだけでなく,実行結果を記述すること. (提出の前に,さまざまな入力データを与えて動作テストを繰り返そう.)

余裕のある人は,反復を利用して, その他の計算(剰余,べき乗,階乗,等)についてもプログラミングしてみよう. また,負の値も使えるようにしてみるのもよいだろう.

レポート提出 注意事項: 以下の点についても厳しくチェックする:


(c) 2015, yanagawa@kushiro-ct.ac.jp