ループの重要性については前回の解説テスト解答で理解して頂けたと思います。今回はループの動作について、もう少し突っ込んで解説していきたいと思います。例によって VS2022 のステップトレースで動作確認を推奨します。

--
いきなりここに飛んで来ちゃった人は、よろしければ下記からご覧ください。
C言語基礎講座インデックス

  • ループの流れを変化させる

ループ中に条件が揃って、もうループをしなくても良いという状況はよくあります。その時、ループから抜け出るには break; を用います。break; を使うと一番内側のスコープから抜けるという動作となります。ただ、if の {} は同一空間と見なされますので、そこから抜けるという動作にはなりません。

もう一つ、重要な役割が continue; です。これは現在のループに関しては処理済みなので、次のループに移行したい際に用います。個人的には break; よりも continue; のほうがループ内ではよく使用します。理由はこれを使うと {} のネストが浅くなるためです。
ネストとは階層の事です。コンパイラ系の言語では、私は "~だったら処理をする" という記述ではなく、 "~じゃなかったら処理をしない" という流れで組む事が多いです。どうしてかは、同じ処理をしている下記の処理を見比べてみてください。

for (int i = 0; i < 100; ++i) { if (id == targetId) { if (x >= 0 && y >= 0 && x < w && y < h) { if (counter % 2 == 0) { printf ("●"); } } } }

for (int i = 0; i < 100; ++i) { if (id != targetId) continue; if (x < 0 || y < 0 || x >= w || y >= h) continue; if (counter % 2 != 0) continue; printf ("●"); }

後の記述だと、実際の動作部分が左側で表示されているため、視線があまり左右に移動しないのです。これは、長時間プログラミングする際の眼精疲労の軽減に繋がります。何より、何をやっているかが理解しやすいと私は思っています。

だいぶ余談になってしまいました。continue; を実行すると、上記の場合は、for の加算処理部に処理が移ります。continue; の使い方はこの例が殆どですね。まれに switch の中で使う事もあります。例えば、こんな感じです。

int num; for (int i = 1; i <= 10; ++i) { printf("%d回目の数値入力: ", i); rewind(stdin); scanf_s("%d", &num); switch (num) { case 0: printf("(´・_・`)\n\n"); continue; case 1: printf("(´゚д゚`)\n\n"); continue; } printf("(´・ω・`)\n\n"); }

これは入力された数値で、顔文字の出力を変えているのですが、指定数値の場合は、顔文字を表示した後で、次のループに戻っています。参考例としては無理矢理ですね😓 上記のプログラムは私ならこうします。

int num; for (int i = 1; i <= 10; ++i) { printf("%d回目の数値入力: ", i); rewind(stdin); scanf_s("%d", &num); switch (num) { case 0: printf("(´・_・`)"); break; case 1: printf("(´゚д゚`)"); break; default: printf("(´・ω・`)"); break; } printf("\n\n"); }

ということで、continue; の説明としては最後は蛇足でしたね… 😓😓😓※ プロ御用達のトランシーバー。複数台のクルマで移動する時や行楽地で別行動時の連絡用として大変お手軽です。


  • ループをネストする

if 等の {} でスコープが指定できる処理では、その中にプログラムを記述する事が出来ました。当然、ループでも記述が可能です。例えば、塗りつぶしは下記のようになります。

for (int y = 1; y <= 25; ++y) { for (int x = 1; x <= 80; ++x) { printf(LOCATE, y, x); switch (rand() % 3) { case 0: printf(FRED); break; case 1: printf(FGREEN); break; case 2: printf(FBLUE); break; } printf("●"); } }

この例では、y のループの中に x のループを作成しています。このような状態がネストです。日本語では入れ子とか階層構造とか言われるようですが、パッと言えるので私は断然ネストと呼ぶ派です😁

スコープは外のスコープから隔離されている別空間です。そして、スコープが閉じられると、そのスコープ内で定義された変数などは全て抹消されます。ちょっとテストで面白い事をしてみます。上記のプログラムを少し改造して…。

for (int i = 1; i <= 25; ++i) { for (int i = 1; i <= 80; ++i) { switch (rand() % 3) { case 0: printf(FRED); break; case 1: printf(FGREEN); break; case 2: printf(FBLUE); break; } printf("●"); } printf("\n"); }

これ、どういう動作をするでしょうか?これを実行確認もせずにサラっと正解できたら、C言語の動作についてかなり理解していると思います。動作としては良いのですが、こういう記述は混乱を招きますので、推奨しないというか止めてください。ただ、知識としては知っておいて欲しいのです。

これ、その前のプログラムと全く同じ動作をします。変数 i はスコープの内外で別物なのです。普通は同じ名前は宣言しても、再定義エラーとなり実行する事は出来ません。
再定義エラー
ただ、スコープが違うので別変数として扱われる場合は、同一変数とは見なされないので定義する事が出来てしまいます。さらにテストします。

int i = 1000; printf("%d:\n", i); for (int i = 100; i < 100 + 5; ++i) { printf("%d = ", i); for (int i = 1; i < 5; ++i) { printf("%d,", i); } printf("\n"); } printf("%d:\n", i);

これ、実行してみてください。スコープ毎に for の初期化部で新しく宣言しているので、全く別の同じ名前の変数として存在しています。是非ステップトレースで確認して欲しいです。もし、for に int の型宣言がなかったら…、例えば以下のようになっていたら…

int i = 1000; printf("%d:\n", i); for (i = 100; i < 100 + 5; ++i) { printf("%d = ", i); for (i = 1; i < 5; ++i) { printf("%d,", i); } printf("\n"); } printf("%d:\n", i);

一番ネストの内側の for で毎回 i が 1 に初期化されて、5 になったらループを向けて、外側のループで +1 されて 6 になりますが、また内側のループに来た時に 1 に初期化されて…という無限ループになってしまいます。変数名を変えてしまえば、このようなコーディングミスはあり得ないので、動作は分かっていたとしても変数の名前は変えておくべきですね。

※ 高速に外部SSDとして動作するUSBメモリみたいなヤツです。HDDと違い衝撃には原理的に強く、それでいてHDD並みの容量があります。値段もだいぶこなれてきました。データの持ち運びの決定版!