さまざまなデータ処理プログラムを作りながら, 反復制御(for,while,do-while)について理解しよう.
なお,条件式は if 文と共通.
ソースコード記述方法:
int カウンタ変数; // 反復回数を入れるよ
for (カウンタの初期化; 反復条件; カウント) {
繰り返したい処理
}
例1:カウントアップ(0 から 9 まで数え上げる)
int i;
for (i = 0; i < 10; i++) {
printf("%d\n", i);
}
動作:
例2:カウントダウン(9 から 0 まで数え下げる)
int i;
for (i = 9; i >= 0; i--) {
printf("%d\n", i);
}
ソースコード記述方法:
while (反復条件) {
繰り返したい処理
}
動作:最初に反復条件を計算する. もし成立していたらブロック { } 内を処理し while へ戻る.
特殊な反復条件:
ソースコード記述方法:
do {
繰り返したい処理
} while (反復条件);
動作:最初にブロック { } 内を処理し,その後で反復条件を計算する. もし成立していたら,do へ戻る.
反復回数に応じて,for/while/do-while を使い分けよう:
複数の非負整数データを合計するプログラム total.c を 3種類のループで作り分けてみよう.
まずは,ディレクトリの準備:
$ mkdir c-0524 $ cd c-0524 または cd !$
テンプレートをコピーして, ソースファイル total.c の編集を開始:
$ cp ~/tmpl.c total.c # テンプレートを利用 $ vim total.c # エディタは何でも OK(以下同様)
または,Vim 内でテンプレコピーして編集開始:
$ vim ~/tmpl.c :f total.c
#include <stdio.h>
int main(void)
{
int total; // 合計
int x; // データ
int n, i; // データの個数,カウンタ
// データの個数の入力
printf("データの個数 > ");
scanf("%d", &n); // 個数を入力
// データの入力,合計の計算
total = 0; // 合計を初期化
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 5 個の非負整数 > 1 2 3 4 5 合計 = 15
個数の入力が面倒くさいですね. データが何個あるか入力前に数えておく必要があるなんて, 非実用的だろう. このように,for ループだと, 反復回数を最初に決めておかなければならないので仕方がない. 次に,while を使って書き換えてみよう.
...
int main(void)
{
int total;
int x;
/*
int n, i;
// データの個数の入力
printf("データの個数 > ");
scanf("%d", &n);
*/
// データの入力,合計の計算
total = 0;
printf("複数個の非負整数(最後に -1)> ");
x = 0; // 初回の反復条件を成立させるための措置
while (x >= 0) {
scanf("%d", &x);
total += x;
}
total -= x; // 調整(最後の x = -1 は合計すべきデータではない)
// ...本来,このような帳尻合わせはよろしくない
// 結果の表示
...
}
$ cc ... $ ./total 複数個の非負整数 > 1 2 3 4 5 -1 合計 = 15
データ数が多い場合を考えれば, 使い勝手は for 版よりは良くなったよね?
しかし,調整の処理について, 正しく計算するために必要ではあるが,足した値を引いており, 無駄手間となっている. データ入力と合計計算の順序を交換すれば この調整処理は不要となる.改善版:
x = 0;
while (x >= 0) {
total += x;
scanf("%d", &x);
}
// total -= x;
ただし,ループの1回目に,入力するより前に,ゼロを足しており, 不自然かつ非効率だろう.
さらに,この処理手順の場合, 必ず1回以上はループ内の入力 scanf() が実行されるので, do-while の方が適切だ.
...
int main(void)
{
...
// データの入力,合計の計算
printf("複数個の非負整数(最後に -1)> ");
x = 0;
do {
total += x;
scanf("%d", &x);
} while (x >= 0);
// 結果の表示
...
}
...
ソースコード的は while 版とほとんど変わりないが, 入力終了時にブロックの上方向へ戻る必要がなく, 処理手順的には,より効率的だ. しかしまだ,入力前の合計計算という不自然・非効率な処理が残っている.
...x = 0;// 初期化は不要 do { scanf("%d", &x); // やはり,入力が加算前の方が普通だよねー if (x < 0) break; // 入力がマイナスなら反復を止める total += x; // やはり,加算は入力後の方が自然だよねー } while (1); // 無条件反復,というか条件判定をループ内に移設 ...
反復の練習として,掛け算を足し算の反復で計算してみよう.
ソース mul.c:(total.c の Step 1 とほぼ同じ)
#include <stdio.h>
int main(void)
{
int x, y, z; // x * y = z
int i; // カウンタ
printf("非負整数 x, y > "); // 問題を入力
scanf("%d %d", &x, &y);
z = ...; // 積を初期化
// 反復回数は既知なので for文
for (i = 0; i < y; i++) { // y 回繰り返す
z = ...; // x を足す
}
printf("%d * %d = %d\n", x, y, z); // 計算結果を表示
return (0);
}
考え方のヒント:x*y = x + x + x + ... = (((0 + x) + x) + x) + ...
$ cc ... $ ./mul 非負整数 x, y > 12 3 12 * 3 = 36
割り算を引き算の反復で計算してみよう. ついでに剰余(余り)も計算するよ.
ソース div.c:(mul.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 = ...; // 商を初期化
m = ...; // 剰余を初期化(x をコピーしておく)
// 反復回数は未知(割り算の結果)だし,ゼロかもしれないので while文
while (m >= y) { // 引き算が非負である限り反復
m = ...; // x から y を引く(実際には m から引く)
z++; // 引き算の回数をカウント
}
printf("%d / %d = %d ... %d", x, y, z, m); // 計算結果を表示
return (0);
}
考え方のヒント:引き算できた回数が計算結果となる.
$ cc ... $ ./div 自然数 x, y > 23 4 23 / 4 = 5 ... 3
(この辺りから今回の課題です.)
総和 Sn = 1 + 2 + 3 + ... + n を求めるプログラム sum.c を作成せよ.
要求仕様:非負整数 n の数値を入力,総和 Sn の数値を表示する.
実行例:
$ ./sum 非負整数 n > 3 総和 Sn = 6 $ ./sum 非負整数 n > 10 総和 Sn = 55
試験の得点データ集合から 最高点(最大値)と最低点(最小値)とを 抽出するプログラム maxmin.c を作成せよ.
要求仕様:ゼロ個以上の整数値 0〜100 を入力, 負の場合に入力を終了. 最大値および最小値を表示する.
実行例:
$ ./maxmin 得点 > 75 得点 > 85 得点 > 40 得点 > -1 最高点 = 85 最低点 = 45
ヒント:
小学生向け教材「九九の計算表」を表示するプログラム 99.c を作成せよ.
実行例:
$ ./99 1の段: 1 2 3 ... 9 2の段: 2 4 6 ... 18 3の段: 3 6 9 ... 27 ... 9の段: 9 18 27 ... 81
質問 Q1〜Q5 に回答し,電子メールで提出せよ.