同じ処理を繰り返し行う †
コンピューターは、同じ処理を繰り返し行うことが得意です。
次のようなプログラムを考えてみましょう。
Hello World!と10回画面に表示する
これをそのままプログラムにすると、次のようになります。
printf("Hello World!\n"); printf("Hello World!\n"); printf("Hello World!\n"); printf("Hello World!\n"); printf("Hello World!\n"); printf("Hello World!\n"); printf("Hello World!\n"); printf("Hello World!\n"); printf("Hello World!\n"); printf("Hello World!\n");
全く同じ命令が10回繰り返されています。
for文は、このような同じ処理を繰り返し行うことができます。
for文は、次の形をしています。
int i; for (i = 1; i <= 繰り返し回数; i++) { 処理 }
ここで、++ は増分演算子(インクリメント演算子)です。 後で詳しく説明します。
for文を使って上のプログラムを書くと、次のようになります(プログラム1)。
int i; for (i = 1; i <= 10; i++) { printf("Hello World!\n"); }
これを実行すると、次のようになります。
luna% a.out Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!
演習1 †
プログラム1を作成し、実行結果を確認せよ。
カウント変数 †
for文で使われている変数 i は、繰り返し回数をカウントするのに使われています。 繰り返し回数をカウントしているので、カウント変数と呼ばれます。 カウント変数の名前は、i でなくても構いません。
上のプログラムに、変数 i の値を出力する命令を追加します(プログラム2)。
int i; for (i = 1; i <= 10; i++) { printf("%d\n", i); printf("Hello World!\n"); }
これを実行すると、次のようになります。
luna% a.out 1 Hello World! 2 Hello World! 3 Hello World! 4 Hello World! 5 Hello World! 6 Hello World! 7 Hello World! 8 Hello World! 9 Hello World! 10 Hello World!
演習2 †
プログラム1をプログラム2に変更し、実行結果を確認せよ。
増分・減分演算子 †
for文の中で使われている ++ という演算子は、増分演算子(インクリメント演算子)といい、カウント変数 i の値を1増やす演算子です。
増分演算子と同じ仲間に、変数の値を1減らす減分演算子(デクリメント演算子)があります。
優先順位 | 演算子 | 使用例 | 意味 |
2 | ++ | i++ | iの値を1増やす |
++i | |||
−− | i−− | iの値を1減らす | |
−−i |
演算子が変数の前についている場合(前置)と変数の後についている場合(後置)では意味が違いますが、後日詳しく勉強します。
for文 †
上では、for文の形を次のように説明しました。
int i; for (i = 1; i <= 繰り返し回数; i++) { 処理 }
正確に説明すると、for文は次の形をしています。
for (初期化; 繰り返し条件; 更新処理) { 処理 }
if文と同じように、for文の波括弧 { } に含まれる命令文が0または1個のとき、この波括弧を省略することができます。
for文は、次のように実行されます。
- 「初期化」を行う
- 「繰り返し条件」を評価する
- 条件を満たしていれば、波括弧 { } の中の「処理」を行い、最後に「更新処理」を行う
- そうでなければ、for文の次の処理に移る
- 2へ戻る
したがって、カウント関数は1からでなく、0から始めることもできます。 また、カウント関数を1ずつ増やすのではなく、2ずつ増やしたり、1ずつ減らすこともできます。
たとえば、10から0までカウントダウンするプログラムは、次のようになります(プログラム3)。
int i; for (i = 10; i >= 0; i--) { printf("%d\n", i); sleep(1); }
演習3 †
プログラム3を作成し、実行結果を確認せよ。
無限ループと強制停止 †
for文は、条件を満たしている間繰り返し続けますので、条件を満たさないことがないと無限に繰り返しを続けます。 これを無限ループといいます。
たとえば、次のプログラムについて考えてみます(プログラム4)。
int i; for (i = 1; i = 10; i++) { printf("%d\n", i); printf("Hallo World!\n"); }
繰り返し条件の演算子を間違えて、代入演算子にしてしまっています。
繰り返し条件を評価すると、カウント変数に10が代入されます。 このとき、結果が0でないので、条件を満たしていると判断され、波括弧の中の処理と更新処理がおこなわれます。 更新処理によってカウント変数は11になりますが、繰り返し条件を評価すると、カウント変数に10が代入されるため、このfor文が停止することはありません。
このようなとき、コントロール・キーを押しながらCキーを押し、プログラムの実行を強制的に停止させます。 コントロール・キーを押しながらCキーを押す操作を、Ctrl+C や ^C と書くことがあります。
演習4 †
プログラム2をプログラム4に変更し、実行して強制停止せよ。
一度も処理しないfor文 †
for文の繰り返し条件が一度も満たされないとき、for文の波括弧内の処理は一度も実行されません。
たとえば、次のプログラムについて考えてみます(プログラム5)。
int i; for (i = 10; i <= 0; i--) { printf("%d\n"); sleep(1); } printf("\n");
カウントダウンするプログラム(プログラム3)において、繰り返し条件の不等号の向きを間違えてしまっています。
これを実行すると、繰り返し条件が最初から満たされていないので、for文の波括弧内のprintf関数とsleep関数は実行されることなく、for文の後の改行を出力するprintf関数だけが実行されます。
演習5 †
プログラム3をプログラム5に変更し、実行結果を確認せよ。
合計を求める †
繰り返し処理を用いてよく行われることの一つが、合計を求めることです。
1から100までの整数の和を求めることを考えてみます。
そのままプログラムにすると次のようになります。
int sum = 1 + 2 + 3 + 4 + 5 + ... + 99 + 100; printf("%d\n", sum);
もちろん、...の部分も省略せずに書かなければなりませんが、ここでは省略してあります。
この処理は、2を加える、3を加える、4を加える ... という加算の繰り返しだと考えることができます。
そこで、この処理を、次のように同じ命令文の繰り返しの形に直します。
int sum = 1; sum = sum + 2; sum = sum + 3; sum = sum + 4; sum = sum + 5; ... sum = sum + 99; sum = sum + 100; printf("%d\n", sum);
ここで、
sum = sum + 2;
は、右辺の計算をしてから代入が行われるため、変数sumの値に2を加えた結果を変数 sum に代入します。 つまり、変数 sum の値に2を加えます。
加える数をカウント変数 i で表すと、繰り返し行われている加算の処理はすべて
sum = sum + i;
という形で書くことができます。
したがって、加える数をカウント変数とし、カウント変数を2に初期化して100まで繰り返すと、次のようなfor文を使ったプログラムにできます(プログラム6)。
int i, sum = 1; for (i = 2; i <= 100; i++) { sum = sum + i; } printf("%d\n", sum);
これでも間違いではありませんが、普通は、合計を表す変数の値を0に初期化して、最初の数も加えていると考えます。
つまり、
int sum = 0 + 1 + 2 + 3 + ... + 99 + 100; printf("%d\n", sum);
と変形して、次のようなプログラムにします(プログラム7)。
int i, sum = 0; for (i = 1; i <= 100; i++) { sum = sum + i; } printf("%d\n", sum);
これだと、合計を求める範囲が変わったときに、カウント変数の初期値と繰り返し条件のところだけを変えるだけで済みます。 (プログラム6は、sumの初期値も変えなければなりません。)
演習6 †
プログラム7を作成し、実行結果を確認せよ。
複合代入演算子 †
合計を求めるプログラムで出てきたような、変数の値にある値を加えて、その結果をその変数に代入するという処理はよく行われます。
sum = sum + i
このため、算術演算と代入を同時に行う演算子として、複合代入演算子が用意されています。
上の処理の場合、加算用の複合代入演算子 += を使って、次のように書くことができます。
sum += i;
したがって、プログラム7は、次のように書くことができます(プログラム8)。
int i, sum = 0; for (i = 1; i <= 100; i++) { sum += i; } printf("%d\n", sum);
算術演算の複合代入演算子には、次のようなものがあります。
優先順位 | 演算子 | 使用例 | 意味 |
15 | += | a += b | aとbの和をaに代入する(加算代入) |
−= | a −= b | aとbの差をaに代入する(減算代入) | |
*= | a *= b | aとbの積をaに代入する(乗算代入) | |
/= | a /= b | aをbで割ったときの商をaに代入する(除算代入) |
剰余算の複合代入演算子はありません。
演習6 †
プログラム7をプログラム8に変更し、実行結果を確認せよ。
繰り返し計算ゲーム †
Lesson 04で作成した計算ゲームを、繰り返し処理を使って、10問出題して正解数を出力するようにします。
問題の作成、出題、入力の受付、正解・不正解の判定を、for文を使って10回繰り返し、正解の数を数えます(プログラム9)。
#include <stdio.h> #include <stdlib.h> #include <time.h> int main(void) { int i, j, k, a, cnt; srand((unsigned int) time(NULL)); cnt = 0; for (k = 1; k <= 10; k++) { i = rand() % 99 + 1; j = rand() % 99 + 1; printf("第%d問: %d + %d = \n", k, i, j); scanf("%d", &a); if (a == i + j) { cnt++; } } printf("%d問正解!\n", cnt); return 0; }
ここで、k は新たに用意したfor文のカウント変数、cnt は正解を数えるための変数です。
演習7 †
プログラム9を作成し、実行結果を確認せよ。
for文のネスト †
for文の中にfor文を入れることをネスト(入れ子構造)といいます。
for文をネストするとき、外側のfor文と内側のfor文では異なるカウント変数を使用します。
つぎのプログラムは、九九の計算をします(プログラム10)。
int i, j, k; for (i = 1; i <= 9; i++) { for (j = 1; j <= 9; j++) { k = i * j; printf("%d * %d = %d\n", i, j, k); } }
外側のfor文のカウント変数が i、内側のfor文のカウント変数が j です。
このプログラムを実行すると、次のようになります。
luna% a.out 1 * 1 = 1 1 * 2 = 2 ... 1 * 9 = 9 2 * 1 = 2 2 * 2 = 4 ... 2 * 9 = 18 3 * 1 = 3 3 * 2 = 6 ... 9 * 8 = 72 9 * 9 = 81
ネストされたfor文では、外側のfor文が[math]N[/math]回、内側のfor文が[math]M[/math]回繰り返されるとき、内側のfor文の波括弧内の処理は全部で[math]N \times M[/math]回繰り返されます。
プログラム10の改行を少なくして、表の形で出力すると、次のようになります(プログラム11)。
int i, j, k; for (i = 1; i <= 9; i++) { for (j = 1; j <= 9; j++) { k = i * j; printf("%3d", k); } printf("\n"); }
内側のfor文では改行せずに、外側のfor文だけで改行しているところがポイントです。
このプログラムを実行すると、次のようになります。
luna% a.out 1 2 3 4 5 6 7 8 9 2 4 6 8 10 12 14 16 18 3 6 9 12 15 18 21 24 27 4 8 12 16 20 24 28 32 36 5 10 15 20 25 30 35 40 45 6 12 18 24 30 36 42 48 54 7 14 21 28 35 42 49 56 63 8 16 24 32 40 48 56 64 72 9 18 27 36 45 54 63 72 81
for文のネストを使うと、次のようなこともできます(プログラム12)。
int i, j; for (i = 1; i <= 10; i++) { for (j = 1; j <= i; j++) { printf("*"); } printf("\n"); }
内側のfor文の繰り返し回数が、外側のfor文のカウント変数の値になっています。 つまり、外側のfor文は「内側のfor文と改行だけを出力するprintf関数」を10回繰り返し実行し、内側のfor文は「* を出力するpriftf関数」を外側のカウント変数の回数だけ繰り返し実行します。 改行は、外側のfor文によって、内側のfor文が実行された後に一回ずつ出力されます。
このプログラムを実行すると、次のようになります。
luna% a.out * ** *** **** ***** ****** ******* ******** ********* **********
演習8 †
プログラム10-12を作成し、実行結果を確認せよ。
まとめ †
回数が決まっている繰り返し処理にはfor文を使います。
for文では、カウント変数の初期化、繰り返し条件、カウント変数の更新処理と、繰り返し行う処理を指定します。
カウント変数の更新処理では、増分演算子(インクリメント演算子)や減分演算子(デクリメント演算子)が用いられることがあります。
繰り返し条件が常に満たされるとき、プログラムを実行すると無限ループになります。 実行時に無限ループになったら、^Cでプログラムの実行を強制停止します。
for文を用いて合計を求める処理がよく行われます。 合計を求めるときは、合計を表す変数を0に初期化し、複合代入演算子で更新します。
for文で繰り返し行う処理にfor文を用いることを、for文のネスト(入れ子構造)といいます。 ネストされたfor文では、外側のfor文と内側のfor文で異なるカウント変数を使用します。
ネストされたfor文において、内側のfor文の繰り返し条件が外側のfor文のカウント変数によって指定されることがあり、このときは外側のfor文のカウント変数の値によって内側のfor文の繰り返し回数が変わります。
練習問題 †
練習問題はこちら。