プログラミングでは「同じ処理を決まった回数だけくり返したい」という場面がとても多いです。
C言語では、その代表的な仕組みとしてfor文があります。
本記事では、C言語初心者の方を対象に、for文の基本からよくある間違い、配列と組み合わせた実用例までを、図を思い浮かべやすいようなイメージとともに丁寧に解説します。
C言語のfor文とは?決まった回数だけくり返す基本
for文で「何回くり返すか」を指定できるしくみ
C言語のfor文は、あらかじめ「何回くり返したいか」が決まっている場合にとても使いやすい構文です。
具体的には、次のような考え方で動きます。
1つの変数(カウンタ変数)に、最初の値を入れてスタートし、その変数を少しずつ変化させながら、ある条件を満たしている間だけ処理をくり返します。
for文のイメージを言葉で図にすると、次のような流れになります。
- 初期化式で「スタート地点」を決める
- 条件式を評価して「まだ続けるか」を判断する
- 本体の処理(ブロック)を実行する
- 変化式で「次の値」に進める
- 2に戻って条件を再評価する
このサイクルが、条件式がfalseになるまでくり返されます。
while文との違い
C言語にはwhile文という、似た役割のくり返し構文もあります。
両者の大きな違いは、次のように整理できます。
- for文
あらかじめ「スタート」「終わり」「変化のしかた」がはっきりしているときに書きやすい構文です。ヘッダ部に初期化式・条件式・変化式をまとめて書くため、決まった回数のループに向いています。 - while文
「いつ終わるか明確には決めにくいが、ある条件が満たされている間だけ続けたい」といった場合に使うことが多いです。処理の途中で条件が変わって終わるようなループに適しています。
同じ処理はfor文とwhile文のどちらでも書けることが多いですが、「回数が決まっているならfor文」「条件のみで制御するならwhile文」と覚えておくと整理しやすくなります。
for文が活躍する代表的な場面
for文は、次のような場面でよく使われます。
1つ目は配列の全要素を順番に処理したいときです。
たとえば、10人分の点数を足し合わせる、全員の点数を画面に表示するなどの処理です。
2つ目はある回数だけ処理をくり返したいときです。
たとえば、10回だけメッセージを表示する、100回だけ乱数を発生させて統計を取るなどです。
3つ目は二次元配列や表形式のデータを処理するときです。
行と列を2重のfor文(ネストしたfor文)で回して、表全体をくまなく処理できます。
このように、C言語の実用的なプログラムでは、for文はほぼ必ず使われると言ってよいほど重要な構文です。
for文の基本構文と3つの書き方ルール
C言語のfor文の基本構文
まずは、C言語のfor文の基本的な書き方を確認します。
for (初期化式; 条件式; 変化式) {
/* くり返したい処理(ループ本体) */
}
実際のコード例として、1から5までの数字を表示するプログラムを示します。
#include <stdio.h>
int main(void) {
int i; /* ループで使うカウンタ変数 */
/* 1から5までを表示するfor文 */
for (i = 1; i <= 5; i++) {
printf("%d\n", i);
}
return 0;
}
1
2
3
4
5
ここでは、i = 1が初期化式、i <= 5が条件式、i++が変化式です。
これら3つの部分を理解することが、for文を使いこなすうえでの3つの書き方ルールにつながります。
初期化式・条件式・変化式の役割を図でイメージ
for文のかっこの中には、3つの式をセミコロン;で区切って書きます。
それぞれ、次のような役割を持ちます。
- 初期化式
ループの最初に1回だけ実行される部分です。カウンタ変数にスタートの値を代入するのが一般的です。 - 条件式
ループを続けるか終えるかを決める判定部分です。true(真)なら本体の処理へ、false(偽)ならループを終了します。 - 変化式
本体の処理を1回実行するたびに呼ばれ、次のループに向けてカウンタ変数を更新します。多くの場合はi++やi--のような1ステップの増減を行います。
言葉でループの「図」を描くと、次のような流れをイメージできます。
- 初期化式を実行
- 条件式を評価
2-1. 条件式がfalseなら、ループを終了して次の処理へ
2-2. 条件式がtrueなら、本体の処理へ進む - 本体の処理を実行
- 変化式を実行
- 再び2へ戻る
「スタート → チェック → 本体 → 次の値へ → チェックに戻る」という輪をぐるぐる回しているイメージを持つと理解しやすくなります。
カウンタ変数(i, jなど)の決め方と型
for文ではカウンタ変数と呼ばれる変数を使って、何回目のくり返しかを管理します。
よく使われる名前はiやj、二重ループではiとj、三重ループではi、j、kのようにします。
カウンタ変数の型は、基本的にはintで問題ありません。
配列のインデックスや回数を数える用途ではintがもっとも一般的です。
#include <stdio.h>
int main(void) {
int i; /* カウンタ変数は通常intで宣言します */
for (i = 0; i < 3; i++) {
printf("i = %d\n", i);
}
return 0;
}
i = 0
i = 1
i = 2
近年のCではfor (int i = 0; ...)のように、for文のかっこの中でカウンタ変数を宣言する書き方もよく使われます。
ただし、コンパイラやCのバージョンによっては対応していない場合もあるため、学習初期のうちは外で宣言する形から慣れていくと安心です。
インクリメント・デクリメント(++ / –)の使い方
カウンタ変数を1ずつ増減させるときには、インクリメント(cst-code>++)とデクリメント(cst-code>–)という演算子が便利です。
i++は「iの値を1増やす」i--は「iの値を1減らす」
基本的なfor文では、次のように使います。
#include <stdio.h>
int main(void) {
int i;
/* 0から4までを表示(増えていくループ) */
for (i = 0; i < 5; i++) { /* i++ で1ずつ増える */
printf("up: %d\n", i);
}
/* 5から1までを表示(減っていくループ) */
for (i = 5; i > 0; i--) { /* i-- で1ずつ減る */
printf("down: %d\n", i);
}
return 0;
}
up: 0
up: 1
up: 2
up: 3
up: 4
down: 5
down: 4
down: 3
down: 2
down: 1
増えていくループではi++とi < 上限、減っていくループではi--とi > 下限を組み合わせるのが定番パターンです。
条件式がfalseになったときの処理の流れ
for文は、条件式がfalseになった瞬間にループを終了します。
ポイントは「いつ条件式が評価されるのか」です。
- ループに入る前に、まず初期化式が実行されます。
- 次に、ループ1回目に入る前に条件式が評価されます。ここで
falseなら、本体は1回も実行されません。 - 1回本体が実行されるごとに、変化式が実行されたあと、再び条件式が評価されます。
- あるタイミングで条件式が
falseになったら、その時点でループを抜けて、for文の次の文へ進みます。
次のサンプルでは、条件がfalseになったあとにどこへ進むのかを確認できます。
#include <stdio.h>
int main(void) {
int i;
for (i = 1; i <= 3; i++) {
printf("ループ中: i = %d\n", i);
}
printf("ループ終了後: i = %d\n", i);
return 0;
}
ループ中: i = 1
ループ中: i = 2
ループ中: i = 3
ループ終了後: i = 4
この例では、i <= 3がfalse(つまりiが4になったとき)にループを抜け、その後のprintfに進んでいることがわかります。
C言語でよくあるfor文の間違いと正しい書き方
セミコロンの付け忘れ・付けすぎによるバグ
for文ではセミコロン;の位置が非常に重要です。
次の2つのパターンのミスがよく起こります。
1つ目はかっこの中のセミコロンの付け忘れです。
/* 間違いの例: セミコロンが1つ足りない */
for (i = 0 i < 10; i++) { /* i = 0 と i < 10; の間に ; が必要 */
printf("%d\n", i);
}
このようなコードはコンパイルエラーになります。
初期化式・条件式・変化式の3つを必ず;で区切る必要があります。
2つ目はfor文の後に余計なセミコロンを付けてしまうミスです。
/* 間違いの例: for文の末尾に余計なセミコロン */
for (i = 0; i < 5; i++); /* ← このセミコロンが余計 */
{
printf("%d\n", i);
}
このコードはコンパイル自体は通りますが、意図とは違う動作になります。
実際に動きを確認してみます。
#include <stdio.h>
int main(void) {
int i;
printf("間違ったfor文の例:\n");
for (i = 0; i < 5; i++); /* ここで空ループが終わってしまう */
{
printf("i = %d\n", i); /* このブロックは1回だけ実行される */
}
return 0;
}
間違ったfor文の例:
i = 5
このコードでは、実際にはfor ( ... );という中身が空のループが5回まわったあと、ブロック部分はループとは無関係に1回だけ実行されてしまいます。
for文の直後にはセミコロンを書かないことを意識しましょう。
無限ループになるfor文
for文でも、書き方を間違えると無限ループになってしまいます。
代表的なケースは次の2つです。
1つ目は条件式が常にtrueになってしまうケースです。
/* 間違いの例: i が増えないため、条件がずっと true のまま */
for (i = 0; i < 10; ) { /* 変化式がない */
printf("%d\n", i);
/* i を増やし忘れている */
}
この場合、iの値が変わらないため、条件i < 10はいつまでもtrueのままになります。
正しくは、変化式や本体内でiを変化させる必要があります。
2つ目は条件式をわざと省略してしまうケースです。
/* 条件式を省略したfor文は、条件式が常にtrueとみなされる */
for (i = 0; ; i++) {
printf("%d\n", i);
}
条件式を空にすると、C言語では「常にtrue」と解釈されるため、これも無限ループになります。
もちろん、意図的に無限ループを書きたいときに使うこともありますが、初心者のうちは条件式を省略しない方が安全です。
初期化式や変化式の書き忘れパターン
for文のかっこの中に書く3つの式は、実はどれも省略可能です。
しかし、省略した意味を理解していないとバグの原因になります。
たとえば、初期化式を書き忘れると、次のようになります。
int i = 0;
/* 初期化式を省略したfor文 */
for (; i < 3; i++) {
printf("%d\n", i);
}
この場合、初期値はfor文の外で設定されるため、iの値が何であるかによってループ回数が変わります。
同じプログラムの中でiを他の用途に使っていると、予想外の挙動につながります。
また、変化式を書き忘れると、先ほどの例のように無限ループの原因になります。
どうしても省略したい特別な理由がない限り、初心者のうちは3つともきちんと書くことをおすすめします。
配列の範囲外アクセスにつながるインデックスミス
配列とfor文を組み合わせるときに特に多いミスが、インデックスの範囲を1つ間違えることです。
配列int a[5];の場合、有効なインデックスは0から4までです。
次のようなfor文は範囲外アクセスとなり危険です。
/* 間違いの例: i <= 5 と書いてしまい、a[5] にアクセスしてしまう */
for (i = 0; i <= 5; i++) {
a[i] = 0; /* a[5] は存在しない */
}
正しくは、条件式をi < 5と書きます。
/* 正しい例: i < 5 なら 0〜4 の5要素だけにアクセスする */
for (i = 0; i < 5; i++) {
a[i] = 0;
}
次のプログラムでは、配列の範囲内と範囲外を意図的に扱って、挙動の違いを確認する例を示します(ただし、範囲外アクセスは未定義動作であり、本来は行ってはいけないことです)。
#include <stdio.h>
int main(void) {
int a[5];
int i;
/* 正しい範囲でのアクセス */
for (i = 0; i < 5; i++) {
a[i] = i * 10;
}
printf("正しい範囲での値:\n");
for (i = 0; i < 5; i++) {
printf("a[%d] = %d\n", i, a[i]);
}
/* 間違った範囲でのアクセス(例として記載、実際には避けるべき) */
printf("\n<mark style="background-color:rgba(0, 0, 0, 0);color:#cf2e2e" class="has-inline-color"><strong>※注意: 次は範囲外アクセスの例です(実際のプログラムでは書かないでください)。</strong></mark>\n");
for (i = 0; i <= 5; i++) { /* a[5] にもアクセスしてしまう */
printf("a[%d] にアクセスしようとしています\n", i);
/* 実際には a[i] を読む/書くと未定義動作になります */
}
return 0;
}
出力結果(一例):
正しい範囲での値:
a[0] = 0
a[1] = 10
a[2] = 20
a[3] = 30
a[4] = 40
※注意: 次は範囲外アクセスの例です(実際のプログラムでは書かないでください)。
a[0] にアクセスしようとしています
a[1] にアクセスしようとしています
a[2] にアクセスしようとしています
a[3] にアクセスしようとしています
a[4] にアクセスしようとしています
a[5] にアクセスしようとしています
配列のサイズがNなら、添字は0〜N-1までというルールを常に意識してfor文を書くことが大切です。
ネストしたfor文で間違いやすいカウンタ変数の使い方
二重、三重のfor文(ネストしたfor文)を書くときには、カウンタ変数を混同しないことが重要です。
よくあるミスとして、内側のループで外側のカウンタ変数を誤って使ってしまうケースがあります。
/* 間違いの例: 内側のループでも i を増やしてしまっている */
for (i = 0; i < 3; i++) {
for (j = 0; j < 2; i++) { /* 本当は j++ と書くべき */
printf("i = %d, j = %d\n", i, j);
}
}
このコードでは、内側のループの変化式でi++としてしまっているため、外側のループのカウンタが意図しないタイミングで増えてしまうことになります。
結果としてループ回数がおかしくなったり、無限ループになったりします。
正しいネストしたfor文の例を見てみましょう。
#include <stdio.h>
int main(void) {
int i, j;
/* 3行2列の座標を表示する例 */
for (i = 0; i < 3; i++) { /* 行を表すループ */
for (j = 0; j < 2; j++) { /* 列を表すループ */
printf("(%d, %d) ", i, j);
}
printf("\n"); /* 1行分終わるたびに改行 */
}
return 0;
}
(0, 0) (0, 1)
(1, 0) (1, 1)
(2, 0) (2, 1)
このように、外側はi、内側はjというように、役割をはっきり分けることで、ネストしたfor文も読みやすく、安全に書くことができます。
配列とfor文の組み合わせ方
配列の全要素をfor文で順番に処理する方法
配列とfor文は非常に相性がよく、配列の全要素を順番に処理する場合はfor文が定番の書き方です。
次の例では、整数配列の全要素を表示する処理をfor文で実装しています。
#include <stdio.h>
int main(void) {
int scores[5] = { 80, 65, 90, 75, 88 };
int i;
printf("配列scoresの全要素を表示します:\n");
for (i = 0; i < 5; i++) { /* iは0から4まで */
printf("scores[%d] = %d\n", i, scores[i]);
}
return 0;
}
配列scoresの全要素を表示します:
scores[0] = 80
scores[1] = 65
scores[2] = 90
scores[3] = 75
scores[4] = 88
配列のすべての要素に対して同じ処理をしたいときには、このようなfor文のパターンが基本形になります。
配列の要素数とfor文の条件式
配列の要素数をそのまま数字で書いてしまうと、あとでサイズを変更したときにfor文も修正し忘れてバグになることがあります。
そこで、配列の要素数を定数として定義しておくと安全です。
#include <stdio.h>
#define SIZE 5 /* 配列の要素数をマクロ定数で定義 */
int main(void) {
int scores[SIZE] = { 80, 65, 90, 75, 88 };
int i;
for (i = 0; i < SIZE; i++) {
printf("scores[%d] = %d\n", i, scores[i]);
}
return 0;
}
scores[0] = 80
scores[1] = 65
scores[2] = 90
scores[3] = 75
scores[4] = 88
このように、条件式にはi < 要素数を書くと決めておくことで、配列の範囲外アクセスを防ぎやすくなります。
また、C言語ではsizeof演算子を使って、配列の実際の要素数を計算することもできます。
#include <stdio.h>
int main(void) {
int scores[] = { 80, 65, 90, 75, 88 }; /* 要素数は5だが省略 */
int i;
int size;
/* 配列全体のバイト数 ÷ 1要素のバイト数 で要素数を求める */
size = sizeof(scores) / sizeof(scores[0]);
printf("scoresの要素数は %d です\n", size);
for (i = 0; i < size; i++) {
printf("scores[%d] = %d\n", i, scores[i]);
}
return 0;
}
scoresの要素数は 5 です
scores[0] = 80
scores[1] = 65
scores[2] = 90
scores[3] = 75
scores[4] = 88
このテクニックを使うと、配列のサイズを変更してもfor文側を自動的に追従させることができるため、保守性が高くなります。
合計値・平均値を求めるfor文の書き方
for文と配列を組み合わせると、合計値や平均値を求める処理も簡単に書けます。
次の例では、5人分のテストの点数の合計と平均を求めています。
#include <stdio.h>
#define SIZE 5
int main(void) {
int scores[SIZE] = { 80, 65, 90, 75, 88 };
int i;
int sum = 0; /* 合計値を入れる変数 */
double average = 0; /* 平均値を入れる変数 */
/* 合計値を求める */
for (i = 0; i < SIZE; i++) {
sum += scores[i]; /* sum = sum + scores[i]; と同じ意味 */
}
/* 平均値を求める(キャストして小数の計算にする) */
average = (double)sum / SIZE;
printf("合計点: %d\n", sum);
printf("平均点: %.2f\n", average); /* 小数点以下2桁で表示 */
return 0;
}
合計点: 398
平均点: 79.60
このように、初期値を0にしておき、for文で1つずつ足し込んでいくのが合計値を求める基本パターンです。
平均値は整数同士の割り算だと小数が切り捨てられてしまうため、(double)でキャストしてから割り算することがポイントです。
二次元配列とfor文
二次元配列を処理するときには、ネストしたfor文を使うのが一般的です。
二次元配列は「行」と「列」を持つ表のような構造なので、外側のfor文で行を、内側のfor文で列を回すイメージで書きます。
次の例では、3行4列の二次元配列を初期化して、すべての要素を表示しています。
#include <stdio.h>
#define ROW 3 /* 行数 */
#define COL 4 /* 列数 */
int main(void) {
int matrix[ROW][COL];
int i, j;
/* 二次元配列を初期化: 値は (行番号 + 列番号) * 10 にする */
for (i = 0; i < ROW; i++) {
for (j = 0; j < COL; j++) {
matrix[i][j] = (i + j) * 10;
}
}
/* 二次元配列の中身を表示する */
printf("matrixの中身:\n");
for (i = 0; i < ROW; i++) {
for (j = 0; j < COL; j++) {
printf("%3d ", matrix[i][j]); /* %3d で桁をそろえる */
}
printf("\n");
}
return 0;
}
matrixの中身:
0 10 20 30
10 20 30 40
20 30 40 50
ここでも、行数と列数をマクロ定数で定義し、for文の条件式でその定数を使うことで、範囲外アクセスを防ぎやすくなっています。
for文を使いやすくするマクロ・定数
for文と配列を組み合わせるときには、マクロ定数や定数変数を活用することで、コードの見通しと安全性が大きく向上します。
代表的な工夫として、次のようなものがあります。
1つ目は配列のサイズをマクロ定数にしておく方法です。
先ほどの#define SIZE 5のように、サイズを1か所にまとめておけば、あとで要素数を変えたくなったときにも変更が楽になります。
2つ目は複数の配列で同じ要素数を使う場合に、共通の定数名を使う方法です。
たとえば、クラスの人数をSTUDENT_NUMのような定数名にしておくと、for文の条件式から用途がわかりやすくなります。
#include <stdio.h>
#define STUDENT_NUM 5 /* 学生の人数を表す定数 */
int main(void) {
int scores[STUDENT_NUM] = { 80, 65, 90, 75, 88 };
int i;
int sum = 0;
for (i = 0; i < STUDENT_NUM; i++) {
sum += scores[i];
}
printf("学生%d人の合計点は %d 点です\n", STUDENT_NUM, sum);
return 0;
}
学生5人の合計点は 398 点です
3つ目として、プロジェクトによってはARRAY_SIZE(a)のようなマクロを定義して、配列の要素数を簡単に計算できるようにすることもあります。
#include <stdio.h>
/* 配列aの要素数を求めるマクロ */
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
int main(void) {
int scores[] = { 80, 65, 90, 75, 88 };
int i;
int size = ARRAY_SIZE(scores);
for (i = 0; i < size; i++) {
printf("scores[%d] = %d\n", i, scores[i]);
}
return 0;
}
scores[0] = 80
scores[1] = 65
scores[2] = 90
scores[3] = 75
scores[4] = 88
このようなマクロや定数を上手に使うことで、for文の条件式が「何を表しているのか」を明確にしつつ、バグを防ぎやすいコードを書くことができます。
まとめ
for文は、C言語で決まった回数だけ処理をくり返したいときの基本構文です。
初期化式・条件式・変化式の3つをセットとして考え、カウンタ変数の増減と終了条件を頭の中で「図」にしながら書くと理解が進みます。
while文との違いや、セミコロンの誤り、範囲外アクセスなどの典型的なミスを意識して避けつつ、配列や二次元配列との組み合わせ、マクロ定数の活用を練習していくことで、for文は確実に使いこなせるようになります。
