C言語で「同じ処理を10回繰り返す」といった、回数が決まっている反復にはfor文が最適です。
この記事では、0開始・未満条件の基本形から、1〜10のような区間の数え上げ、配列全走査、逆順(カウントダウン)、break/continue、型とスコープの注意点、よくあるミスまで、実用的なサンプルとともに丁寧に解説します。
for文とは
回数が決まった反復に向く制御構文
for文は、初期化・継続条件・更新の3つをひとまとめに記述できるため、決まった回数のループや、配列・文字列の走査に最もよく使われます。
条件が偽になった時点でループを終了します。
構文の基本
3つの式の意味
- 初期化: ループ開始時に一度だけ評価され、カウンタ変数の初期値を設定します。
- 条件: ループ継続の判定を毎回行います。偽になったら終了します。
- 更新: 各周回の末尾で評価され、カウンタを進めたり減らしたりします。
for (初期化; 条件; 更新) {
/* 繰り返したい処理 */
}
波括弧の省略
本体が1文のみなら{}
は省略可能ですが、可読性・保守性のため基本的に{}
を付けることをおすすめします。
基本形(10回繰り返す)
0開始・未満条件が定番
回数が決まっているときは、カウンタを0から始め、条件を「未満」にするのが定番です。
これにより「0〜(回数-1)」の範囲が素直に表現できます。
#include <stdio.h>
int main(void) {
for (int i = 0; i < 10; i++) {
printf("i = %d\n", i); // 0~9で10回
}
return 0;
}
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
なぜ「未満」が安全か
上限を「未満」にすると、配列の要素数n
に対して0 <= i < n
という自然な条件が書け、配列外参照のバグを避けやすくなります。
1から10まで数えたい場合
開始値と条件を付け替える
「1〜10」のように人間の数え方で出力したい場合は、開始値を1、条件を「<=(以下)」にします。
#include <stdio.h>
int main(void) {
for (int i = 1; i <= 10; i++) {
printf("%d回目\n", i); // 1~10で10回
}
return 0;
}
1回目
2回目
3回目
4回目
5回目
6回目
7回目
8回目
9回目
10回目
Off-by-one(±1ミス)に注意
- 0〜9の10回:
for (int i = 0; i < 10; i++)
- 1〜10の10回:
for (int i = 1; i <= 10; i++)
条件演算子の向きを取り違えると1回多い/少ないバグになりやすいです。
回数を変数・定数で指定する
マジックナンバーを避ける
const定数で指定
回数をコード内に直接書くのではなく、意味のある名前の定数にしましょう。
#include <stdio.h>
int main(void) {
const int N = 5; // 必要に応じて意味のある名前に
for (int i = 0; i < N; i++) {
printf("%d ", i);
}
printf("\n");
return 0;
}
0 1 2 3 4
マクロでビルド時に固定
ビルド時に確定したい場合はマクロも有効です。
#include <stdio.h>
#define N 5
int main(void) {
for (int i = 0; i < N; i++) {
printf("%d ", i);
}
printf("\n");
return 0;
}
0 1 2 3 4
引数や入力で可変回数にする
外部から回数を与えるとテストや再利用がしやすくなります。
入力値には上限チェックを設けましょう。
#include <stdio.h>
int main(void) {
int n;
if (scanf("%d", &n) != 1) return 1; // 入力失敗
if (n < 0 || n > 1000000) return 1; // 過大・不正入力を拒否
for (int i = 0; i < n; i++) {
// ここに処理
}
return 0;
}
上限チェックの意義
極端に大きな回数は処理時間やリソースを圧迫します。
ユーザー入力に従う場合は妥当な上限を設けるのが実務では必須です。
配列や文字列の長さで回す
配列の全要素を処理する
C配列は長さ情報を持たないため、自分で要素数を求めます。
静的配列ならsizeof
で安全に計算できます。
#include <stdio.h>
int main(void) {
int a[] = {1, 2, 3, 4, 5};
size_t n = sizeof a / sizeof a[0]; // 要素数
for (size_t i = 0; i < n; i++) {
printf("%d\n", a[i]);
}
return 0;
}
1
2
3
4
5
可変長配列・ポインタには注意
関数引数として配列を受け取るときは「配列はポインタに退化」するため、sizeof
で要素数は分かりません。
要素数を別引数で渡すのが基本です。
文字列の長さで回す
strlen
で長さ(終端のヌル文字を除く)を取得し、その長さだけ回します。
#include <stdio.h>
#include <string.h>
int main(void) {
const char s[] = "abc";
size_t len = strlen(s);
for (size_t i = 0; i < len; i++) {
printf("%c", s[i]);
}
printf("\n");
return 0;
}
abc
カウントダウン(逆順)ループ
intのインデックスで逆順
int
の範囲なら「0以上」を条件にしてカウントダウンできます。
#include <stdio.h>
int main(void) {
for (int i = 9; i >= 0; i--) { // 0~9の逆順で10回
printf("%d ", i);
}
printf("\n");
return 0;
}
9 8 7 6 5 4 3 2 1 0
size_t(符号なし)の安全な逆順
配列長やstrlen
と相性の良いsize_t
は符号なし型です。
そのためi >= 0
は常に真になり危険です。
慣用的に「for (size_t i = n; i-- > 0; )
」と書きます。
#include <stdio.h>
int main(void) {
int a[] = {10, 20, 30, 40};
size_t n = sizeof a / sizeof a[0];
// i は n-1 から 0 へ
for (size_t i = n; i-- > 0; ) {
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
40 30 20 10
なぜ「i– > 0」なのか
後置デクリメントは比較評価の後に減少するため、i
が0の周回は実行されず、i
がn-1
から0まで安全に1回ずつ回ります。
ループ変数の型とスコープ
インデックスとサイズはsize_tが基本
配列の要素数、strlen
、sizeof
はsize_t
型を返します。
インデックスもsize_t
に合わせると、型不一致や符号なし/ありの比較が原因の警告や不具合を避けやすくなります。
printf
でsize_t
を出力する場合のフォーマット指定子は%zu
です。
C99以降はfor内で宣言可能
C99以降ではfor (int i = 0; …)
のようにループ変数をその場で宣言できます。
古い規格(C90など)ではブロック先頭で事前宣言が必要です。
#include <stdio.h>
int main(void) {
for (int i = 0; i < 3; i++) { // C99以降
printf("%d ", i);
}
printf("\n");
// ここでは i は見えない(スコープはfor文の内部)
return 0;
}
0 1 2
スコープはforのブロック内
C99以降では、ループ変数はfor文のブロック内に限定されます。
同名の変数を別のループで再利用しても衝突しません。
読みやすさ・誤用防止のためにもスコープは狭く保ちましょう。
break / continue の最小例
特定の条件でスキップ・打ち切り
continue
はその周回の残りをスキップ、break
はループ全体を打ち切ります。
#include <stdio.h>
int main(void) {
for (int i = 0; i < 10; i++) {
if (i == 3) continue; // 3だけスキップ
if (i == 8) break; // 8で打ち切り(0,1,2,4,5,6,7まで)
printf("%d ", i);
}
printf("\n");
return 0;
}
0 1 2 4 5 6 7
多用は避ける
break
やcontinue
は便利ですが、条件が散らばると読みづらくなります。
複雑化する場合は条件の整理や関数抽出を検討してください。
よくあるミスと注意点
Off-by-one(±1ミス)
- やりたいこと: 0〜9を10回
- 正:
for (int i = 0; i < 10; i++)
- ありがちな誤り:
i <= 10
(11回になる)
- 正:
- やりたいこと: 1〜10を10回
- 正:
for (int i = 1; i <= 10; i++)
- ありがちな誤り:
i < 10
(1〜9で9回)
- 正:
型の不一致
配列長やstrlen
はsize_t
です。
int
と比較したり、符号なしと符号ありを混在させると警告やバグの原因になります。
インデックスはsize_t
、出力には%zu
を使いましょう。
符号なしでの逆方向ループ
size_t i; for (i = n - 1; i >= 0; i--)
は停止しません(i >= 0
が常に真)。
「for (size_t i = n; i-- > 0; )
」や「for (size_t i = n; i > 0; i--) { /* i-1 を使う */ }
」など安全な条件に書き換えます。
ループ変数の更新忘れ・本体での改変
更新をforの第3式に集約し、本体でループ変数を変更しないのが定石です。
本体での加減はバグの温床になります。
回数が大きすぎる
ユーザー入力や外部データに依存する回数は、妥当な上限を設けましょう。
DoS的な極端入力で処理が止まるのを防げます。
まとめ
固定回数の反復は、0開始・未満条件の「for (int i = 0; i < N; i++)」が基本形です。
区間を1〜10のようにしたい場合は開始値と条件(<=
)を調整します。
配列や文字列の走査にはsize_t
を用い、逆順では「i-- > 0
」や「i > 0
+i-1
」といった安全な条件を選びましょう。
可読性と安全性のため、マジックナンバーを避け、上限チェックやスコープの短縮、型の整合性に注意して書くことが大切です。