制御構造2(反復)

さまざまなデータ処理プログラムを作りながら, 反復制御(for,while,do-while)について理解しよう.

教科書の該当範囲:第6章

基礎知識

反復の分類

条件式は if 文と共通.


for 文:計数反復

ソースコード記述方法:

int  カウンタ;		// 反復回数を入れるよ
for (カウンタの初期化; 反復条件; カウント) {
	繰り返したい処理
}

例1:カウントアップ(0 から 9 まで数える)

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

動作:カウンタを初期化 i = 0 する. 反復条件 i < 10 が成立しているので, printf() を実行し,「0」を表示する. カウントアップ i++ により i = 1 とし, 反復条件へ戻る.

なぜ 10 を使うのか?0 から 9 までの 10 回なので. (もちろん,条件式を i <= 9 としてもよい.)

なお,i++ は i = i + 1 の短縮版ね. (実は,微妙に違いますが,今は気にしないでおく.)

そして,このループの終了後,カウンタ i の値は 9 ではなく, 10 になっていることに注意しよう.

例2:カウントダウン(9 から 0 まで数える)

int	i;
for (i = 9; i >= 0; i--) {
	printf("%d\n", i);
}

ここで,i-- は i = i - 1 の短縮版ね.(同上.)


while 文:前判定条件反復

ソースコード記述方法:

while (反復条件) {
	繰り返したい処理
}

動作:最初に反復条件を計算する. もし成立していたらブロック { } 内を処理し while へ戻る.

特殊な反復条件:


do-while 文:後判定条件反復

ソースコード記述方法:

do {
	繰り返したい処理
} while (反復条件);

動作:最初にブロック { } 内を処理し,次に反復条件を計算する. もし成立していたら,do へ戻る.


反復の使い分け基準

反復回数に応じて...


実習の準備

いつもの通り,作業用ディレクトリを準備しよう:

$  cd
$  mkdir  c-0523
$  cd  c-0523

合計計算プログラム

複数の非負整数データを合計するプログラム total.c を 3種類のループで作り分けてみる.

Step 1. for ループ版
$  vim  total.c	# エディタは何でも OK
#include <stdio.h>

int main(void)
{
	int	total = 0;	// 合計
	int	x;		// データ
	int	n, i;		// データの個数,カウンタ

	// データの個数の入力
	printf("データの個数 > ");
	scanf("%d", &n);		// 個数を入力

	// データの入力,合計の計算
	printf("%d 個の非負整数 > ", n);
	for (i = 0; i < n; i++) {
		scanf("%d", &x);	// データを入力
		total += x;		// 合計を加算,total = total + x;
	}

	// 結果の表示
	printf("合計 = %d\n", total);
	return (0);
}
$  cc  total.c  -o  total

$  ./total
データの個数 > 5
3 個の非負整数 > 1  2  3  4  5
合計 = 15

個数の入力が面倒くさいですね. データが何個あるか入力前に数えておく必要があるなんて, 実用的ではない. for だと,個数を最初に決めておかなければならないので... while を使って書き換えてみよう.

Step 2. while ループ版
...
int main(void)
{
	int	total = 0;
	int	x;
/*
	int	n, i;

	// データの個数の入力
	printf("データの個数 > ");
	scanf("%d", &n);
*/

	// データの入力,合計の計算
	printf("複数個の非負整数(最後に -1)> ");
	x = 0;		// 初回の反復条件を成立させるための措置
	while (x >= 0) {
		scanf("%d", &x);
		total += x;
	}
	total -= x;	// 調整(最後の x = -1 は合計すべきデータではない)

	// 結果の表示
	...
}
...
$  ./total
複数個の非負整数 > 1  2  3  4  5  -1
合計 = 15

データ数が多い場合を考えれば, 使い勝手は for 版よりは良くなったよね?

しかし,調整の処理が(正しく計算するために必要ではあるが) 無駄手間(足してから引いている)となっている. データ入力と合計計算の順序を交換すれば この調整処理は不要となる.改善版:

	x = 0;
	while (x >= 0) {
		total += x;
		scanf("%d", &x);
	}
//	total -= x;

さらに,この処理手順の場合, 必ず1回以上はループ内の入力 scanf() が実行されるので, do-while の方が適切だ.


Step 3. do-while ループ版
...
int main(void)
{
	...
	// データの入力,合計の計算
	printf("複数個の非負整数(最後に -1)> ");
	x = 0;
	do {
		total += x;
		scanf("%d", &x);
	} while (x >= 0);

	// 結果の表示
	...
}

ソースコード的は while 版とほとんど変わりないが, 入力終了時にブロックの上方向へ戻る必要がなく, 処理手順的には,より効率的だ. しかし,まだ入力前の合計計算という不自然と無駄は残っている. (この辺りの問題点については,次回に解決予定.)


反復による掛け算

掛け算を足し算の反復で計算してみよう. mul.c:

#include <stdio.h>

int main(void)
{
	int	x, y, z;	// x * y = z
	int	i;		// カウンタ

	printf("非負整数 x, y >;	// 問題を入力
	scanf("%d %d", &x, &y);

	z = 0;
	// 反復回数は既知なので for文
	for (i = 0; i < y; i++) {	// y 回繰り返す
		z = z + x;		// x を足す
	}
	printf("%d * %d = %d\n", x, y, z);	// 計算結果を表示
	return (0);
}

考え方のヒント:x*y = x + x + x + ... = (((0 + x) + x) + x) + ... それぞれの括弧がループ1回分, 括弧内の計算結果が z ね.


反復による割り算

割り算を引き算の反復で計算してみよう. ついでに剰余(余り)も計算するよ. div.c:

#include <stdio.h>

int main(void)
{
	int	x, y, z;	// x / y = z
	int	m;		// 剰余 m = x % y

	printf("自然数 x, y > ");	// 問題を入力
	scanf("%d %d", &x, &y);

	z = 0;
	m = x;
	// 反復回数は未知(割り算の結果)だしゼロかもしれないので while文
	while (m >= y) {	// 引き算できるだけ反復
		m = m - y;		// x から y を引く
		z++;			// 引き算の回数をカウント
	}
	printf("%d / %d = %d ... %d", x, y, z, m);	// 計算結果を表示
	return (0);
}

考え方のヒント:引き算できた回数が計算結果となる.


本日の課題

  1. 総和 Sn = 1 + 2 + 3 + ... + n を求めるプログラム sum.c を作成せよ.
  2. 要求仕様:非負整数 n の数値を入力データとする.

  3. 試験の得点データの最大値と最小値を求めるプログラム maxmin.c を作成せよ.
  4. 要求仕様:整数 0〜100 を入力データとする. (余裕ある人はこの条件を無視してハードルを上げてみよう. どんな整数でもよいことにする. 最初のデータを最大値・最小値の初期値とすればよい. 終了方法には工夫とか妥協が必要かも.)

提出:


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