前の講座ではif文を始めif-else文, switch文などの条件分岐についてみていきました。内容的にもだいぶプログラミングらしさが出てきて大変だったり、面白かったりするところですね。
今回勉強していくのはfor文やwhile文です。これらは繰り返し処理なんて呼ばれる文です。条件分岐と合わせてプログラミングで最も重要な文法です!(理論上、if-else文とfor文の構文さえかければ、どんな大きなプログラムでも書けることになります。あくまで理論上ですが)
また、以前出てきた配列がfor文ととても相性が良いので、配列の見直しを兼ねた記事にしてみました。
前回の復習
前回は条件演算子、論理演算子、if文、if-else文、switch文について勉強しました。具体的な演算子はif文【C言語講座 #9】の方で確認してください。条件演算子と論理演算子を組み合わせて作られる式を条件式と呼ぶのでした。この条件式というのはif文やif-else文にも登場しましたね。
if文やif-else文というのは条件分岐を行うことができるプログラミングの文法で以下のような形が基本形になります。
if(条件式) {
処理1;
処理2;
}
if(条件式) {
処理1;
処理2;
} else {
処理3;
}
また、switch文というものについても触れました。(if文で頭の整理が追いついていない人は後回しにしても大丈夫です)
前回の練習問題の解答例
A問題
a, b, cの値は互いに異なることが保証されているものとする。
入力
出力
mには一番大きな数字を、tには何枚目のカードであるかを出力してください。
入力例1
出力例1
一番大きな数字は3で、その数字が書かれたカードは3枚目です。
入力例2
出力例2
一番大きな数字は50で、その数字が書かれたカードは2枚目です。
解答例
#include <stdio.h>
int main() {
int a, b, c;
scanf("%d %d %d", &a, &b, &c);
if(a > b && a > c) {
printf("%d 1\n", a);
} else if(b > a && b > c) {
printf("%d 2\n", b);
} else {
printf("%d 3\n", c);
}
return 0;
}
競技プログラミングの一番簡単な問題でよく出てくる3つの数字の中の最大値を求めるタイプの問題です。
if文を使って書けるようにしておきましょうね。
B問題
入力
入力例1
60 40
出力例1
最も安い運賃はAからBでバスを使い(20円)、BからCで電車を使った(40円)場合です。
入力例2
39000 1000
出力例2
解答例
#include <stdio.h>
int main() {
int ans = 0, bus[2], train[2];
scanf("%d %d", &bus[0], &train[0]);
scanf("%d %d", &bus[1], &train[1]);
if(bus[0] < train[0]) ans += bus[0];
else ans += train[0];
if(bus[1] < train[1]) ans += bus[1];
else ans += train[1];
printf("%d\n", ans);
return 0;
}
考え方は非常に簡単で、AからB、BからCに行くのにかかる運賃でそれぞれ安い方を選択して足せばそのまま答えになります。
処理が1文しかなかったので波括弧は省略して書きました。
for文
今回の内容はfor文を含め繰り返し処理について学習していきます。最初に繰り返し処理というのはどういうものなのかをイメージしてもらうために簡単な例を用いてみていきます。
例えば1から100までの数字が並んだ数列の総和を求めたいとします。1 + 2 + 3 + …… + 99 + 100を計算するためには足し算を99回行わなければなりません。素直にプログラムを書いたら大変なことになりそうですね……。
ここで登場するのがfor文と呼ばれるものです。for文を使って1から100までの和を求めるコードを書いてみると以下のように短く書くことができます。
#include <stdio.h>
int main() {
int sum = 0;
for(int i = 1; i <= 100; i++) {
sum += i;
}
printf("%d\n", sum);
return 0;
}
このように似たような処理をまとめて実行することが得意です。
具体的にどんなことができるのかをイメージしてもらったところで、for文の書き方の勉強をしていきましょう。for文の基本形は以下のようになります。(更新式と書いてある部分は説明のためにそう呼ぶことにしました!!正式名称ではないので注意してくださいね)
for(初期化式; 条件式; 更新式) {
処理1;
処理2;
}
これを用いることで条件式が成り立っている間は繰り返し処理が実行されるようになります。そうは言われてもこの形を見ただけではさっぱりわからないと思うので、もう少し細かくfor文の文法を見て行くことにします。
最初に初期化式について述べます。これは初期化を覚えていれば話が早いのですが、忘れている人の方が多いと思うので軽く復習を兼ねて初期化式を説明します。まず初期化というのは、変数を作って値を代入することを指します。例えば以下のような操作のことを言います。
int number = 0;
初期化式というのは、初期化を行っている文のことを指しているわけですね。
次に条件式ですが、これは前回の記事でも登場したので大丈夫でしょう。(わからない人はif文【C言語講座 #9】で復習をしておいた方が良いかもしれません)条件式が成り立っている間は波括弧の中の処理を実行し続けます。条件式がずっと成り立ってfor文の波括弧内の処理が永遠に実行されることを無限ループと言います。無限ループのソースコードを実行してしまったら『Controlキー + C』で強制終了できます。後で無限ループを試してみるコラムをつけておくので、興味があればどうぞ。
最後に更新式の部分についてみていきます。ここに書かれるのは波括弧の処理が1周した後に行われる処理を書くことになります。大抵の場合は条件式や初期化式の部分に使われている変数の値を変更する処理を書きます。(無限ループを防ぐため)
それでは試しにサンプルコードをひとつ実行してプログラムの処理を追いながら学習していきます。
#include <stdio.h>
int main() {
for(int i = 1; i <= 10; i++) {
printf("%d回目の処理\n", i);
}
return 0;
}
実行結果
1回目の処理
2回目の処理
3回目の処理
4回目の処理
5回目の処理
6回目の処理
7回目の処理
8回目の処理
9回目の処理
10回目の処理
実際に実行した時のfor文の処理がどうなっているのかを1ステップごと追って理解していきましょう。
プログラムがコンパイルされ実行される場合main関数から処理が始まります。今回のサンプルコード(tc1001.c)では3行目のmain関数から処理が始まりますね。
4行目に移動し、for文が出てきました。for文にたどり着くと一番最初に行われるのは初期化式の部分です。(初期化という名前の通りfor文の最初に一回だけ実行されます)その次に条件式の部分が実行され、条件式が成り立っていればfor文の波括弧の中を、成り立っていなければfor文の波括弧を実行をせずにfor文の終わりから処理が再開されます。
波括弧内の処理が一通り実行し終えた後で、更新式が行われます。
これがfor文の1周目になります。更新式が実行された後は条件式の部分を確認し、条件式が成り立っている場合はfor文の波括弧を再度実行し成り立っていない場合はfor文の処理を終えfor文のすぐ後ろから処理を再開します。
このように波括弧内の処理が終わるたびに更新式を実行し、条件式が成り立っているかを毎周チェックをして成り立っている間は波括弧内の処理をひたすら繰り返す構文になります。更新式や条件式を書き間違えると無限ループ(常に条件式が成り立つfor文)になりかねないので気を付けるようにするとともに、無限ループになって処理が止まった時の対応も身につけておきましょう。(columnにて)
また、配列との相性が非常に良いので、配列を絡めたサンプルコードをひとつ試してfor文に慣れていきましょう。
#include <stdio.h>
int main() {
int score[] = {3, 6, 4, 7, 6};
for(int i = 0; i < 5; i++) {
printf("%d点\n", score[i]);
}
return 0;
}
実行結果
3点
6点
4点
7点
6点
このように配列の各要素にアクセスすることができます。要素数5の配列は0番目から4番目までの5つに値が格納されているため、iの値が0, 1, 2, 3, 4の時のみ実行されるように、初期値と条件式を書いています。
また以下のようなfor文の使い方をすることで、配列の中に値を格納しその中から一番大きい値を探すこともできます。(競プロで度々出てくるしこの使い方は知っておいた方が良いかも……。練習問題のA問題が解ければOK!)
#include <stdio.h>
int main() {
int answer = 0;
int weight[10];
for(int i = 0; i < 10; i++) {
scanf("%d", &weight[i]);
}
for(int i = 0; i < 10; i++) {
if(answer < weight[i]) answer = weight[i];
}
printf("%d\n", answer);
return 0;
}
実行結果
1 3 4 69 4 23 1 54 6000 4 //入力
6000
for(初期化式; 条件式; 更新式) {
処理1;
処理2;
}
for文の中で初期化している変数iはどの参考書、学習サイトを見ても変数iを用いています。これはイテレータ(iterator)という繰り返しを意味する単語の頭文字から来ているようです。ただの変数なのでfor文のところで使っている変数名は予約語でなければ自由につけられます!どこの本にもiで統一されているからiしか使えないと思い込まないように一応書いておきました!
ちなみに、for文の中にfor文を書くこともできます。練習問題のC問題に載せておくのでよかったら挑戦してみてくださいね。
while文
for文と同様、繰り返し処理を行うことができる構文にwhile文があります。こちらは非常に明快で、条件式が成り立っている間だけ処理を続けることができます。while文の基本形は以下のようになります。
while(条件式) {
処理1;
処理2;
}
for文の時にあった初期化式や更新式はなくなったわけではなく、while文の前に初期化式を、while文の中に更新式を書くことで同じ処理が可能になります。
int i = 0;
while(条件式) {
処理1;
処理2;
i++;
}
こんな感じで書けばfor文と同じになることがわかると思います。
つまり適した文法を使うとソースコードが簡潔でわかりやすくなります!このメリットは何にも変え難い価値があります!!
今までの話を少し整理するためにサンプルコードを用意しました。ぜひ試してみてください。
#include <stdio.h>
int main() {
int sum = 0;
/*for文ver*/
int card[10] = {1, 5, 3, 5, 3, 12, 7, 3, 7, 8};
for(int i = 0; i < 10; i++) {
sum += card[i];
}
printf("%d\n", sum);
/*while文*/
sum = 0;
int j = 0;
while(j < 10) {
sum += card[j];
j++;
}
printf("%d\n", sum);
return 0;
}
実行結果
54
54
このようにfor文でもwhile文でも同じ処理が書けます。ただ、while文の方が行数が多くなるのでコードの見通しがほんの少し悪くなります。
次のサンプルコードはfor文よりもwhile文向けのコードです。問題の設定は『順番に並んでいる子供たちに飴玉を配っていく際に何人まで渡せるか』というシンプルなものです。しかし実際にシミュレートしないと繰り返す回数がわからない場合によく用いられます。
#include <stdio.h>
int main() {
int sum = 40; //手持ちの飴の総数
int candy[] = {1, 4, 3, 5, 2, 5, 7, 6, 2, 9, 1, 3, 7, 2, 1, 5, 7}; //子供達が要求する飴の数
int i = 0;
while(candy[i] <= sum) {
sum -= candy[i];
i++;
}
printf("%d\n", i);
return 0;
}
実行結果
9
0番目の子に1個、1番目の子に4個、2番目の子に3個と渡していくと9人の子供にしか飴は配れないことがわかりました。
while(条件式) {
処理1;
処理2;
}
無限ループについてのコラムを書いておきます。無限ループというのは、実行したプログラムが何らかの理由で終わらない状態を指します。いつまでも条件式が成り立っているfor文やwhile文などが原因で更新式か条件式のどちらかが間違っています。このコラムではそんな無限ループが発生した時の対処について触れておきます。
まずは無限ループを実際に体験してみましょう!下のサンプルコードを実行してみてください。
#include <stdio.h>
int main() {
for(;;) {
printf("無限ループ!\n");
}
return 0;
}
実行結果
無限ループ!
無限ループ!
無限ループ!
無限ループ!
……(以下省略)
無限ループを止めるためには、『Ctrl(Control)キー + C』を押します。何かを書き間違えて無限ループになってしまった時はこれで実行終了できるんだなということを知っておきましょう!
ただ全ての無限ループが悪という訳ではありません。例えば下のようなサンプルコードはあえて無限ループを用いている例になります。
#include <stdio.h>
int main() {
int i = 0;
char str[1000];
while(1) {
scanf("%c", &str[i]);
if(str[i] == '.') break;
i++;
}
printf("%s\n", str);
return 0;
}
実行結果
I am a cat. //入力
I am a cat.
I like drawing pictures. //入力
I like drawing pictures.
このようにループ回数が定まっていない場合に、無限ループとbreak文を掛け合わせた書き方が用いられることもあります。今回のサンプルコードは、入力された文字列を一文字ずつ受け取り、『ピリオド(.)』が出てきたらループを抜け出すようにif文を使っています。break文は現在実行しているブロックから抜け出す際に用いられるものです。第9回の記事で紹介したswitch文のところでも登場しましたね。
break文についてはこの記事の最後のほうにcontinue文と一緒に勉強してもらうので、今はスルーしてもらって構いません。
do-while文
繰り返し構文の3つ目はdo-while文になります。この構文はfor文やwhile文と比べるとあまり使われる機会が多くありません。ただ、これを他の構文で書くとコードが冗長になりがちなので余裕があれば覚えておきましょう。
do-while文は名前にwhileが入っていることからもわかる通り、while文の亜種みたいな文法です。なのでほとんどの部分がwhile文と一緒です。なのでここではdo-while文の基本形とwhile文との違いをメインに説明していきます。
do-while文の基本形は以下の形になります。条件式を書く場所を間違えやすいので、whileの後ろに書くと覚えておきましょう。最後のセミコロンも忘れないようにしてくださいね。
do {
処理1;
処理2;
} while(条件式);
while文との違いはループを最低でも1回実行するかどうかという部分にあります。具体的な例を挙げてみます。例えば『あるwebページで会員登録などでメールアドレスを入力する欄があったとします。ここに入力された形式が正しいメールアドレスのフォーマットに従っているかを判定し、誤ったフォーマットの場合は再入力させるプログラムを作成したいとします。』このような問題設定を考えると入力は少なくとも1回は行われる必要がありますよね? この時にwhile文を用いたらどうなるでしょうか。大体の流れはこんな感じになると思います。
入力してもらう;
while(入力が正しくない時) {
入力してもらう;
}
別にこれでも悪くはないのですが、入力してもらう処理が2箇所にあるのをなんとかまとめてスッキリしたいですよねー。
これを解決するのがdo-while文という訳です。do-whole文を用いて書くとこんな感じになります。
do {
入力してもらう;
} while(入力が正しくない時);
最低でもループを1回実行する性質をもつdo-while文をうまく用いることでコンパクトなコードが書けました!もちろんdo-whileとwhileの本質的な違いはそれくらいなので、無理に覚えなくてもwhileだけで十分代用できますが、覚えて使いこなせるとコンパクトで綺麗なコードが書けることがあるので余裕があれば知っておくと良いでしょう。
#include <stdio.h>
int main() {
int password;
do {
printf("パスワードを4桁の数字で入力してください: ");
scanf("%d", password);
} while(password < 1000 || 9999 < password);
printf("%d\n", &password);
return 0;
}
実行結果
パスワードを4桁の数字で入力してください: 123 //入力
パスワードを4桁の数字で入力してください: 1000 //入力
1000
1000以上9999以下の数字が入力された時、do-while文の条件式を満たさなくなり、繰り返し処理が終わります。
do {
処理1;
処理2;
} while(条件式);
break文とcontinue文
この記事の最後のトピックとしてbreak文とcontinue文について学んでいきます。内容的には非常に簡単ですが、とても便利でよく使われるものなのでマスターしておきましょう。
まずはbreak文とcontinue文についてどういう文法なのかを先に説明しておきます。break文とは現在のブロック(今回の場合は繰り返し処理)から抜け出す処理で、continue文は繰り返し処理で現在の周を中断して次の周に飛ばす処理です。
言葉で説明するとまどろっこしい表現になっている気がするので、簡単なサンプルコードを用いて実験をしてみましょう。
#include <stdio.h>
int main() {
for(int i = 1; i <= 100; i++) {
if(i == 3) continue;
if(i == 10) break;
printf("%d回目のループ\n", i);
}
return 0;
}
実行結果
1回目のループ
2回目のループ
4回目のループ
5回目のループ
6回目のループ
7回目のループ
8回目のループ
9回目のループ
実行結果を元にcontinue文とbreak文の挙動について考えてみましょう。まず実行結果で気になるのは3回目のループが飛ばされている点です。ここで怪しい場所を探してみると5行目にif文があり、iが3の時にcontinue;を実行するようプログラムされています。どうやらcontinue文の影響で3回目のループが飛ばされたようです。
また、サンプルコード中のfor文の条件式の部分をみると『i <= 100』と書かれています。これはiが100以下の間は繰り返し処理を実行することを表しています。しかし実行結果には9回目のループまでしか出力されていません。すなわち10回目以降の繰り返し処理が中断されています。ここで先ほど同様怪しい場所を探してみるとどうやら6行目のif文が関係ありそうです。iが10のときbreak文を実行するよう書かれていることから、ループから抜け出す処理の原因はbreak文にあったことがわかります。
実行結果とサンプルコードを見比べながらの説明でしたが、時にこういった視点でソースコードを読まなければならないことがあるので、たまにはこういう出力からコードを覗くのもやってみると面白いかもしれませんね。
continue文とは繰り返し処理でその周の処理を飛ばして次の周にスキップするために用いられる文である。
break文もcontinue文も通常if文とセットで使い、『特定のものが見つかったときループ(繰り返し処理)を抜け出すor処理を1周飛ばす』といった具合に用いる。
まとめ
今回の記事では繰り返し構文を中心に勉強しました。for文, while文, do-while文に加えて、break文とcontinue文にも触れました。前回の条件分岐と今回の繰り返し構文さえ理解できれば後はほとんど難しい部分は残っていません!(残っている大変なのはポインタくらい……!)ということでここまで記事を読んでくださっている方はプログラミング学習の7割くらいを終えていると言っても過言ではないでしょう!!
今回の内容をざっとまとめておきます。各構文の書き方は記事中の該当箇所を見直してくださいね。
- 繰り返し処理とは同じ処理や似たような処理をまとめて何回も繰り返し実行する構文のことを指す。
- 繰り返し項羽文にはfor文, while文, do-while文の3つがある。
- break文とcontinue文は繰り返し処理においてループから抜け出したり、現在の処理を飛ばしたりできる文法でif文などの条件分岐とセットで用いられる。
次回は一番最初に書いている#include <stdio.h>についての記事を書く予定です。
練習問題
A問題
答えは32bit以内におさまります。
入力
はi日目の歩数を表しており、1以上10000以下であることが保証されています。
出力
には7日間の歩数の合計を出力してください。
入力例1
出力例1
6000 + 5600 + 4349 + 7995 + 4597 + 7543 + 5999の計算結果が答えになります。
入力例2
出力例2
B問題
入力
出力
には以下の偶数の和を出力してください。
入力例1
出力例1
7以下の偶数は2, 4, 6が該当します。なので2 + 4 + 6を計算したものが答えになります。
入力例2
出力例2
C問題
ex問題
ABC 005 B おいしいたこ焼きの食べ方【AtCoder】
ABC 002 B 罠【AtCoder】
ABC 006 B トリボナッチ数列【AtCoder】
最後まで記事を見ていただきありがとうございます。また別の記事でお会いできることを祈っております。