繰り返し(ループ loop)(教科書6章)
「♪くりかえす このポリリズム」
繰り返しとは
世の中には同じ事を何度も繰り返すことが多々ある。ちょっと考えただけでもたくさん思い浮かぶだろう。曲を例にとると、1番、2番、3番……と同じメロディになっていることがよくある。曲によってはまったく同じメロディで繰り返すものもあるし、間奏がが入ったり少々アレンジの入るものもある。
楽譜ではこのような繰り返しを反復記号を使って書く。例えばこのページで紹介されているように。反復記号を使うことで、同じメロディを何度も書く手間を省くことができるし、曲の流れ(構造)も理解しやすくなる。
プログラムでも同様である。
繰り返し=実行箇所の逆戻し
プログラムは上から下に、順に実行されると説明した(順次実行)。前回説明した条件分岐では、条件に応じて処理を飛ばすことができるが、順次実行の原則は変わらない。
では、実行している行を既に実行した行に戻すとどうなるか?
次のプログラムはその例である。goto loop;は実行する行をラベルloop:に飛ばすことを意味する。
注意!! goto文はプログラムの流れを読みにくくする危険が高いため、一部の例外を除いて使うべきではない。どんな複雑な繰り返しでも、次に説明するwhile文あるいはfor文(およびbreak文とcontine文)だけ使って書くことができる。説明のためにここではgoto文を使うが、存在自体を忘れてしまっても構わない。
/***** 繰り返しの説明のためのサンプルコード 注意!! goto文は使うべきではない!! *****/ #include <stdio.h> int main() { loop: // <- goto loop;から戻ってくる場所を指定するためのラベル printf("Hello World!\n"); goto loop; // loop: に戻る -> return 0; // ここには到達しない }
この処理の流れをフローチャートで描くと次のようになる:
処理を順に追っていくと、printf文でHello World!と表示し、loop:と書かれたところに戻る。すると次にまたprintf文でHello World!と表示し、loop:と書かれたところに戻り、またprintf文で……(以下無限に繰り返す)
注意!! このプログラムは無限に同じ場所を繰り返すのでプログラムが終了しない(無限ループと呼ぶ)。無限ループに陥ったプログラムを終了させるには、Control+c で強制的に中断させる必要がある。
while文(教科書6.2節)
繰り返しを記述するための基本的な文法がwhile文である。while文は次のように書く:
while (条件式) { 実行文1; 実行文2; 実行文3; ..... }
実行文が1つしかない場合は
while (条件式) 実行文;
のように{ }を使わずに書くことができるが、if文と同様に { } を常に書く方が間違いが少ないし、実際的には実行文が1つしかないwhile文はあまり使われない。
条件式は、繰り返しを継続するか終了するかの判定に使われる。条件式が成り立っている場合(条件式の値が真の場合)は{ }の中に処理を進める。条件式が成り立っていない場合(条件式の値が偽の場合)は{ }の中は飛ばし、} の次の行に処理を進める。{ }の中の処理を進め、}に到達したらwhile文に処理を戻し、再び条件式で判定する。
フローチャートで描くと次のようになる:
最初から条件が成立していない場合は、繰り返し(while文の{ }の中身)は1回も実行されないことに注意。
while文による繰り返しが始まって、有限回繰り返した後、繰り返しが終了するには、最初は条件式が成り立っていて、繰り返していくうちに条件式が成り立たなくなる必要がある。
例えば次のようなwhile文は条件式が常に成り立つので無限ループになる:
while (1 == 1) { // 常に真 printf("Hello World!\n"); }
while文の条件式が繰り返すうちに成り立たなくなるには、条件式で使っている変数の値が繰り返しの処理によって変化しなければならない。
これはwhile文の条件式で繰り返しを終了させる場合の話である。break文を使って繰り返しを抜け出す方法ではこの限りではない。
繰り返し回数を数えるwhileループ
例えば、Hello World!を5回だけ繰り返して表示することにする。紙にペンで書く際、普通は回数を数えるだろう。ではプログラムで回数を数えるにはどのようにしたらよいか?そこで、次のように考えてみる:
回数を数えるための変数numberを用意し、最初は0にしておく。1回"Hello World!"を表示するたびにnumberの値を1増やす。numberの値が5になったら繰り返しを終了する。
1から数え始めてもよいのだが、後に習う配列と組み合わせて使うことを想定すると0から数える習慣を身につけた方がよい。主なプログラミング言語では0から数えるようになっている。
while文の条件式は成り立っている間は繰り返す、であるから、「numberの値が5になったら終了」というのを「numberの値が5未満なら続ける」と言い換える必要がある。つまりこうなる:
回数を数えるための変数numberを用意し、最初は0にしておく。1回"Hello World!"を表示するたびにnumberの値を1増やす。numberの値が5未満なら繰り返しを続ける。
「5以下なら」ではないことに注意。「未満」と「以下」とで、下のフローチャートで何回繰り返されることになるか数えてみよう。
「numberの値が5になったら終了」をそのまま裏返すと「numberの値が5でなければ繰り返す」となる。この条件でも間違いではないが、繰り返しの意味的には「numbeの値が5になるまで繰り返す」に相当する「numberの値が5未満なら繰り返す」の方が良い。
この処理の流れをフローチャートで描くと次のようになる:
「numberの値を0にする」には、numberに0を代入すればよい。
numberには最初に0を代入しなければならないことに注意!! C言語では変数を宣言した時点ではどのような値になっているかは決まっていない。このため、何かの値を代入する必要がある(初期化と呼ぶ)。なお、変数宣言と初期化は次のように同時に行うことができる:
int number = 0;
「numberの値を1増やす」には、numberと1を足した値をnumberに代入すればよい。つまり、
number = number + 1;
これは次のようにも書けることは既に説明したとおり:
number += 1;
number++;
練習:"Hello World!"を5行表示するプログラムを完成させよ。
このnumberのような、繰り返し回数を数えるための変数をカウンタ変数と呼ぶ。
do-while文(教科書6.3節)
条件分岐の回の宝箱の例題で1〜3の番号を入力させるプログラムを作成したが、1〜3以外でも入力できてしまっていた。では、入力された値が1〜3でなかったらもう一度入力させるようにしてみよう。
簡単のため最初のプログラムで説明する(今回説明する部分は、どの変化形のプログラムでも同じようになるので、最初のプログラム以外を修正してもよい)。次のプログラムは前回の最初のプログラムである。
/***** treasure.c 宝箱を開けてみる 2015.6.10 M.Minakuchi *****/ #include <stdio.h> int main() { int box; // 選んだ宝箱の番号 printf("宝箱が3つある!どれを開けますか?(1か2か3): "); scanf("%d", &box); if (box == 1) printf("宝箱は罠だった\n"); return 0; }
scanf文で入力された値が1〜3でなかったら1つ前のprintf文に戻すことにする。while文を使うとこうなる(プログラムの一部のみ掲載):
while (box <= 0 || 3 < box) { printf("宝箱が3つある!どれを開けますか?(1か2か3): "); scanf("%d", &box); }
while文の条件の部分、box <= 0 || 3 < box は、「boxの値が0以下か、あるいは、boxの値が3より大きいか」という意味の条件式である。boxは整数型なので、この条件式は「boxの値が1〜3でなかったら」という意味と同じである。||については次回説明する。今はこのように書けばよい、としておこう。
ただし、このままでは不十分である。boxの値は、変数を宣言しただけでは値が不定(どのような値になっているか決まっていない)なので、運悪くboxの値が1〜3であればwhile文の中の処理は飛ばされてしまう。これを防ぐには、確実にwhile文の条件が成り立っている値、例えば0を初期値として代入しておけばよい。
box = 0; while (box <= 0 || 3 < box) { printf("宝箱が3つある!どれを開けますか?(1か2か3): "); scanf("%d", &box); }
もう一つの方法として、while文の代わりにdo-while文を使う方法がある。do-while文の書き方は次のようになる。whileの条件式の後ろに;が必要なことに注意!!
do { 実行文1; 実行文2; 実行文3; ..... } while (条件式);
do-while文の処理の流れをフローチャートで描くと次のようになる:
while文と違って、条件にかかわらず処理({ }の中身)を1回は実行する。このようにすると変数boxの値を初期化する必要がない。
練習:do-whileを使って書き直してみよう。
do-while文を使わなくても、while文で変数の値を適切に初期化するなどの工夫をすることによって同じ処理を書くことは可能である。が、do-while文を使う方がプログラムがすっきりしたり、意図を明確に表現することができる場合もある。必要に応じて使うとよいだろう。
確認課題その1. while文の練習
以下、できあがったら教員に確認を受けること。原則的に易しい順となっているが、順序どおりに取り組む必要はない(for文の説明以降の、確認問題その2.も含めて)。また、多目に用意しているので授業時間内にすべてができることを求めるものではない。Do your best!
確認課題1. 回数を数える
"Hello World! 1回目"のように、現在何回目かを表示するメッセージを7回繰り返すプログラムhelloCount.cを作成せよ。
【実行例】 % gcc -o helloCount helloCount.c % ./helloCount Hello World! 1回目 Hello World! 2回目 Hello World! 3回目 Hello World! 4回目 Hello World! 5回目 Hello World! 6回目 Hello World! 7回目 %
確認課題2. 夢想花
次の歌詞は夢想花(円広志作詞)の一節である。これを繰り返しを使って表示するプログラムmusoubana.cを作成せよ。
とんでとんでとんでとんでとんでとんでとんでとんでとんで
まわってまわってまわってまわる
【実行例】 % gcc -o musoubana musoubana.c % ./musoubana とんでとんでとんでとんでとんでとんでとんでとんでとんで まわってまわってまわってまわる %
ヒント:「とんで」と「まわって」を表示するprintf文を必要な回数だけ繰り返す。「とんで」の部分と「まわって」の部分は別のループにする。
何も表示せずに改行だけしたい場合、
printf("\n");
とすればよい。
確認課題3. 2乗の計算
1から入力された値までの2乗をそれぞれ計算して表示するプログラムsquare.cを作成せよ。
なお、入力された値が0より小さい場合は何も表示しなくてよい(入力のエラーチェックは不要)。
【実行例(下線は入力値の例、入力メッセージはこの通りでなくてもよい)】 % gcc -o square square.c % ./square input number: 7 1 * 1 = 1 2 * 2 = 4 3 * 3 = 9 4 * 4 = 16 5 * 5 = 25 6 * 6 = 36 7 * 7 = 49 % ./square input number: -3 %
ヒント:カウンタ変数を計算に使う。2乗は同じ数値を2回掛け合わせればよい。2乗の計算式を表示するためのprintf関数の書き方(書式指定子と変数をどのように書かなければならないか)も考えよう。
確認課題4. 1からnまでの総和
1から入力された値までの総和を、繰り返しを使って求めるプログラムsum.cを作成せよ。
なお、入力された値が0以下の場合は再度入力させるようにせよ。
【実行例(下線は入力値の例、入力・出力メッセージはこの通りでなくてもよい)】 % gcc -o sum sum.c % ./sum input number: 7 sum = 28 % ./sum input number: -3 input number: 14 sum = 105 %
ヒント:総和を求めるための変数を用意する。入力をやり直させるプログラム例はこの資料で既に解説している。
確認課題5. nの階乗
入力された値の階乗を求めるプログラムfactorial.cを作成せよ。nの階乗は、1からnの整数の積である。入力された値が0以下の場合は再度入力させるようにせよ。
また、大きな値を入力すると表示される結果がおかしくなる(13以上で計算結果が正しくなくなる)が、理由を考察せよ。
【実行例(下線は入力値の例、入力・出力メッセージはこの通りでなくてもよい)】 % gcc -o factorial factorial.c % ./factorial input number: 5 5! = 120 % ./factorial input number: -3 input number: 10 10! = 3628800 %
for文
繰り返し処理でよく使われるパタンの1つに、カウンタ変数を使って繰り返し回数を数える、というものがある。while文を使って書くと次のようなパタンである。
int count; // カウンタ変数 count = 0; // カウンタ変数の値を0にする while (count < 10) { // countが10になるまで繰り返す // 繰り返し処理(具体的なプログラムは省略) count++; // countの値を1増やす }
このプログラムは何回繰り返されるか?countの値はどのように変化するか?
この繰り返しの書き方には、次の3つの部分がある:
【繰り返しを始める前に行う処理】 while (【繰り返しを続ける条件】) { // 繰り返し処理 【1回繰り返すごとに行う処理】 }
繰り返し処理のところに書かれる処理が少ないプログラムでは問題がないが、ちょっと複雑なプログラムになると、どこがこれら3つの部分に相当するかが分かりにくくなる。そこで、C言語にはfor文という書き方が用意されている。for文は次のような文法になる:
for (【繰り返しを始める前に行う処理】; 【繰り返しを続ける条件】; 【1回繰り返すごとに行う処理】) { // 繰り返し処理 }
練習:上のwhile文で書かれたプログラムの断片をfor文を使って書き換えてみよう。
for文の()の中には繰り返しの制御に関係する処理や条件のみを書くことが望ましい。例えば次のプログラムは繰り返す回数を入力させて、入力回数だけhelloと表示するプログラムである:
int count; // カウンタ変数 int repeatNum; // 繰返し回数 printf("繰返し回数を入力してください: "); scanf("%d", &repeatNum); for (count = 0; count < repeatNum; count++) { // 入力された値の回数だけ繰り返す printf("hello\n"); // 繰り返す内容 }
このプログラムは、for文の文法的には次のように書いても間違いではないが、繰り返しの構造が分かりにくいので避けるべきである:
int count; // カウンタ変数 int repeatNum; // 繰返し回数 for (printf("繰返し回数を入力してください: "), scanf("%d", &repeatNum), count = 0; count < repeatNum; printf("hello\n"), count++) { // 入力された値の回数だけ繰り返す // printfはfor文の中に移動させた }
1つの文しか書けないところに複数の処理を書きたい場合は,(カンマ)で区切って書くことができる。しかし、ややこしくなりがちなので無理に使わない方がよいだろう。
for文の基本パタンとしては、次のように使うと覚えておくとよい。基本パタンに当てはまらない複雑な繰り返しはwhile文を使った方が分かりやすくなることも少なくない。
for (【カウンタ変数の初期化】; 【繰り返しを続ける条件】; 【カウンタ変数の値の操作】) { // 繰り返し処理 }
なお、for文のそれぞれの部分は必要がなければ省略可能である。
for文の練習
練習1'〜5'.
確認課題1〜5のプログラムをfor文を使って書き直せ。
確認課題その2. for文
以下の問題はfor文を使って書くこと。余裕があればwhile文でも書いてみよう。
確認課題6. カウントダウン
for文はカウンタ変数を使って繰り返し回数を数える書き方がよく使われるが、これに限っているわけではない。簡単な例で考えてみよう。
入力された値から1まで、1ずつ減らして表示するプログラムcountdown.cを作成せよ。
【実行例(下線は入力値の例、入力メッセージはこの通りでなくてもよい)】 % gcc -o countdown countdown.c % ./countdown input number: 5 5 4 3 2 1 %
ヒント:カウンタ変数を1ずつ減らすようにすればよい。カウンタ変数を用意する代わりに、入力値を格納した変数の値を変化させてもよい。
確認課題7. 奇数のみ数える
1以上100未満の奇数の2乗を表示するプログラムoddSquare.cを作成せよ。
【実行例】 % gcc -o oddSquare oddSquare.c % ./oddSquare 1 9 25 49 ...(途中省略) 9409 9801 %
繰り返し回数を数える考え方で処理の流れを書くと例えば次のようになる。1以上100未満の奇数は50個あることに注意。
このフローチャートを元にoddSquare.cを作成せよ。
考え方としては正しいが、奇数の個数を数えるのはちょっと素直でない。1, 3, 5, 7, 9, ..., 99と奇数のみを作成していき、それぞれの2乗の値を表示する方が自然だろう。この考え方の処理の流れを書くと次のようになる。奇数は2ずつ増えていくことに注意。
このフローチャートを元にoddSquare.cを書き換えよ。
これらはどちらが正しいというものではない。プログラムを書いた人の考え方次第である。ただし、通常はできるだけ理解しやすい書き方を選ぶのがよい。
確認課題8. プロット [advanced]
大きさ600 x 400のウィンドウに、左下を原点(0,0)として、y = x / 2 + 30 となる直線上の点を、x座標が0から10刻みで600まで描くプログラムplot.cを作成せよ。点は、半径1の円として描けばよい(塗りつぶさなくてよい)。
確認課題9. 同心円 [advanced]
大きさ600 x 600のウィンドウに、中心座標が(300, 300)、半径が50, 100, 150, 200, 250の同心円を5つ、繰り返しを使って描くプログラムconcentric.cを作成せよ。
ヒント:カウンタ変数で回数を数えて繰り返す方法と、半径の値を変えながら繰り返す方法とが考えられる。どちらの方法でもよい。カウンタ変数で数える方法の場合は繰り返しの中で、描こうとしている円の半径を計算する必要がある。
本日の提出課題
提出課題1. 漸化式 ,
で表される数列を入力された値の個数分表示するプログラムrecurrence.cを作成せよ。入力された値が0以下の場合は再度入力させるようにせよ。
提出期限:次の土曜日の24:00まで
【実行例(下線は入力値の例、入力メッセージはこの通りでなくてもよい)】 % gcc -o reccurence reccurence.c % ./reccurence input number: 7 1 5 17 53 161 485 1457 %
ヒント:値を表示する処理と次の値を計算する処理の順序を考えてみよう(つまり、最初の項の表示をどうするか)。順序によって若干異なるプログラムとなる。
なお、漸化式を解いて一般項を求める必要はない。というか、繰り返しの練習なので求めてはいけない。プログラムで、初項a0から順に計算させればよい。
提出課題2. 整数値を入力させ、大きさ600 x 600のウィンドウに入力された数の行と列のマス目を描くプログラムgrid.cを作成せよ。入力値のエラーチェックはなくてもよい。なお、場合によってはウィンドウの端に描いた線が見えないことがあるが構わない。
提出期限:次回の授業開始時まで
(入力値が9の例)
ヒント:まず入力された値からマス目の大きさを計算する。繰り返し回数を数える方法と、座標値を変えながら繰り返す方法とが考えられる(同心円の例題と同様)。
計算方法と入力値によっては、下図のように端に余白ができることがあるが構わない(余裕があれば、なぜ余白ができてしまうのか、どのように計算すれば余白ができなくなるか考えてみよう)。