<C言語>繰り返し処理
今回の記事でわかること
- for 文・while 文・do while 文の使い方が分かる
- 同じ処理を何度も繰り返す方法が分かる
繰り返し条件とは
プログラムで「同じ処理を何回も行いたい」と気に使います。例えば、1~10までの数字を順番に表示したい、ユーザーが正しく入力をするまで繰り返し確認したい、配列やリストの中身をすべて処理したい…などの時に使用されます
選び方
繰り返し文 | 説明 |
---|---|
for | 回数が決まっているときに向いている |
while | 条件が真の間繰り返す |
do while | 最低1回は実行したいとき |
for 文
回数が決まっている繰り返しに最適。構文は「初期化; 条件; 更新」の3セットで書きます
#include <stdio.h>
int main(void) {
for(int i = 1; i <= 5; i++) {
printf("i = %d\n", i);
}
return 0;
}
/*出力結果
i = 1
i = 2
i = 3
i = 4
i = 5
*/
i = 1 から i <= 5 まで繰り返す処理を行っております
使い方によっては、偶数回だけ回す、逆順で回す、配列の長さを取得してその回数回すなど条件次第で様々な使い方があります
ネスト
#include <stdio.h>
int main(void) {
for (int x = 1; x <= 3; x++) {
for (int y = 1; y <= 3; y++) {
printf("%d×%d=%d ", x, y, x*y);
}
printf("\n");
}
return 0;
}
/*出力結果(3×3の一部)
1×1=1 1×2=2 1×3=3
2×1=2 2×2=4 2×3=6
3×1=3 3×2=6 3×3=9
*/
ループや条件分岐をさらに別のループ・条件分岐で囲むイメージ
二次元データを扱うときに使用されます(表や座標の作成など)
while 文
条件が真の間だけ繰り返す。回数が読めない処理に向いています
#include <stdio.h>
int main(void) {
int i = 1;
while (i <= 5) {
printf("i = %d\n", i);
i++; // 更新を忘れると無限ループになるため注意
}
return 0;
}
/*出力結果
i = 1
i = 2
i = 3
i = 4
i = 5
*/
私がよく使う場面としては、有効な入力が来るまで繰り返すという処理です。入力チェックに使いやすく、入力で正しいのがくれば繰り返しを抜けるという処理をすれば何回でも入力を促すことができます
また、while(1)と書くと常に true の状態である無限ループの実装もでき、break 等を使用して明示的に抜ける繰り返し方法もあります
do while 文
最低1回は処理を実行してから条件を判定する。メニューUIに向いています
#include <stdio.h>
int main(void) {
int choice;
printf("\n=== メニュー ===\n");
printf("1: 足し算 2: 引き算 0: 終了\n> ");
do {
if (scanf("%d", &choice) != 1) return 0;
switch (choice) {
case 1: printf("足し算を選びました\n"); break;
case 2: printf("引き算を選びました\n"); break;
case 0: printf("終了します\n"); break;
default: printf("無効な選択です\n");
}
} while (choice != 0);
return 0;
}
/*動作例
=== メニュー ===
1: 足し算 2: 引き算 0: 終了
> 2
引き算を選びました
> 5
無効な選択です
...
*/
この処理だと、0以外が入力されている間は繰り返し続けるという処理になっています。
break / continue
処理 | 説明 |
---|---|
break | 今のループを終了 |
continue | 残りをスキップして次の周回へ |
#include <stdio.h>
int main(void) {
for (int i = 1; i <= 10; i++) {
if (i % 3 == 0) continue; // 3の倍数はスキップ
if (i == 8) break; // 8でループを打ち切り
printf("%d ", i);
}
printf("\n");
return 0;
}
/*出力結果
1 2 4 5 7
*/
break は1番内部のループだけに効くため、外側も抜けたい場合はフラグを使うか、処理を関数に分けて return に戻すと安全です
実用パターン
合計や最大値を求める
#include <stdio.h>
int main(void) {
int a[] = {4, 9, 1, 7};
int len = (int)(sizeof(a) / sizeof(a[0]));
int sum = 0, max = a[0];
for (int i = 0; i < len; i++) {
sum += a[i];
if (a[i] > max) max = a[i];
}
printf("sum=%d, max=%d\n", sum, max);
return 0;
}
/*出力結果
sum=21, max=9
*/
逆順に走査する(後ろからチェック)
for (int i = len - 1; i >= 0; i--) {
// a[i] を処理
}
2つのポインタ / カウンタで内側に詰める
for (int i = 0, j = len - 1; i < j; i++, j--) {
// a[i] と a[j] を交換する、など
}
センチネル(特定値が来るまで)
int x;
while (scanf("%d", &x) == 1 && x != -1) {
// -1 が来るまで処理
}
パフォーマンスと可読性のコツ
不変な計算はループの外へ
ループの中で毎回同じ計算をしていると、無駄に処理時間がかかるため、変わらない値は事前に求めて、ループの外に出すのが基本です
#include <stdio.h>
int main(void) {
int arr[100];
int len = sizeof(arr) / sizeof(arr[0]); // 外で1回だけ計算
for (int i = 0; i < len; i++) {
arr[i] = i * 2;
}
return 0;
}
もし len の計算を for の条件に書くと、ループの100回すべてで sizeof を実行することになり、小さなプログラムでは誤差でも、処理回数が多いと処理速度の低下につながります
条件が複雑なら早期 continue でネストを浅くする
ネストが深くなると読みにくくなり、可読性が低下してしまいます。そんなときは「条件を満たさなかったらすぐ飛ばす(continue)」するとすっきりします
// ネストが深い例
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
if (i > 10) {
printf("%d\n", i);
}
}
}
// continueを使った例
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) continue; // 偶数以外はスキップ
if (i <= 10) continue; // 10以下はスキップ
printf("%d\n", i);
}
大事なのはわかりやすさであり、前者はどこでスキップされているのかが分かりにくいと思います。後者は全く同じ処理ではありますが、「条件を満たすときに何をするか」が見やすいと思います
カウンタや上限は const / #define / enum で名前を付ける
マジックナンバー(意味の分からない数字)がコードに出ていると、後から読む人が困ります。おまけに、数年後に自分のコードを見ると自分でもわからないという現象が起きてしまうんです…
名前を付ければ「この数字の意味」が明確になる上、修正もしやすい
#define MAX_USERS 100 // マクロ
const int MAX_SCORE = 60; // 定数
int scores[MAX_USERS];
for (int i = 0; i < MAX_USERS; i++) {
if (scores[i] >= MAX_SCORE) {
printf("合格\n");
}
}
1行でも中かっこ{}をつける
大体の言語は1行であれば{}を省略することができますが、保守性を考えると書いた方が安全です
// 危険な例
if (x > 0)
printf("positive\n");
printf("always\n"); // インデントで勘違いしやすい
// 安全な例
if (x > 0) {
printf("positive\n");
}
printf("always\n");
前者は「x > 0の時だけ両方実行される」と思い込みやすいけれど、実際は2行目は常に実行される
{}があればどこまでが範囲なのかが明確となるため、回収や追加で事故を防げます