C言語の基本|配列とfor文

配列の番号をfor文でたどれば、たくさんのデータも短いコードでまとめて扱える。

配列を使うと、同じ型のデータをひとまとまりにして保存できます。しかし、配列の要素を1つずつ個別に入力したり表示したりしていては、要素が増えるほどコードも長くなってしまいます。

そこで組み合わせたいのがfor文です。

for文の変数を配列の添字として使えば、先頭の要素から最後の要素まで順番に処理できます。入力、表示、合計など、配列を使う多くの処理は、この組み合わせを基本にして作られます。

この記事では、インデックス変数と添字の関係、入力と表示の方法、範囲外アクセスを防ぐ条件式、要素数をマクロで管理する方法を順番に解説します。

配列にfor文を組み合わせる理由

配列は、同じ型の値を複数保存するための仕組みです。たとえば、5日分の運動時間を保存するなら、次のように宣言できます。

int minutes[5];

この配列には、次の5つの要素があります。

要素の順番添字要素の指定
1番目0minutes[0]
2番目1minutes[1]
3番目2minutes[2]
4番目3minutes[3]
5番目4minutes[4]

配列の各要素に対して同じ処理を行うとき、個別にコードを書く方法では手間がかかります。

printf("%d\n", minutes[0]);
printf("%d\n", minutes[1]);
printf("%d\n", minutes[2]);
printf("%d\n", minutes[3]);
printf("%d\n", minutes[4]);

for文を使えば、同じ処理を次のようにまとめられます。

for (int i = 0; i < 5; i++) {
    printf("%d\n", minutes[i]);
}

処理する要素が5個から10個に増えても、printfを何行も追加する必要はありません。繰り返す回数を変更するだけで対応できます。

図1:for文が配列を順番にたどる仕組み

この図から分かること

for文の変数iは、配列の添字としてそのまま利用できます。

iが0ならminutes[0]、iが1ならminutes[1]というように、iを1ずつ増やすことで全要素を順番に処理できます。配列が5要素なら、添字の範囲は0から4までです。

インデックス変数と添字の関係

配列を処理するfor文では、iのように現在位置を表す変数をよく使います。この変数は、インデックス変数と呼ばれます。

for (int i = 0; i < 5; i++) {
    printf("%d\n", minutes[i]);
}

このfor文では、次の順序で処理が進みます。

繰り返しiの値処理する要素
1回目0minutes[0]
2回目1minutes[1]
3回目2minutes[2]
4回目3minutes[3]
5回目4minutes[4]

基本形は次のとおりです。

for (int i = 0; i < 要素数; i++) {
    配列名[i]を使った処理;
}

各部分には、次の役割があります。

記述役割
int i = 0先頭要素の添字0から始める
i < 要素数配列の範囲内にいる間だけ続ける
i++次の要素へ進む
配列名[i]現在の要素を指定する

要素数が5の場合、条件をi < 5とすれば、iの値は0、1、2、3、4となります。これで5個すべてを、範囲内で安全に処理できます。

配列の全要素へ順番に入力する

配列へ入力するときも、インデックス変数を添字として使います。

for (int i = 0; i < 5; i++) {
    scanf("%d", &minutes[i]);
}

scanfには、入力値の保存先となるアドレスを渡します。そのため、配列の要素minutes[i]の前にも&が必要です。

int value;
scanf("%d", &value);

普通の変数へ入力するときに&valueと書くのと同じ考え方です。配列の各要素も、1つずつ見ればint型の変数として扱えます。

表示用の番号と配列の添字は分けて考える

C言語の配列は添字0から始まりますが、利用者へ見せる番号は1から始めたほうが自然な場合があります。

printf("%d日目の運動時間を入力してください > ", i + 1);

このとき、入力先はminutes[i]です。

i画面に表示する番号入力先
01日目minutes[0]
12日目minutes[1]
23日目minutes[2]
34日目minutes[3]
45日目minutes[4]

iは配列内部の位置を指定し、i + 1は人に見せる番号として使います。この2つを混同しないことが大切です。

5日分の運動時間を入力して表示するプログラム

ファイル名:10_4_1.c

#include <stdio.h>

int main(void)
{
    int minutes[5];

    /* 5日分の運動時間を入力する */
    for (int i = 0; i < 5; i++) {
        printf("%d日目の運動時間を入力してください(分)> ", i + 1);
        scanf("%d", &minutes[i]);
    }

    /* 入力された運動時間を順番に表示する */
    printf("\n入力された運動時間\n");

    for (int i = 0; i < 5; i++) {
        printf("%d日目:%d分\n", i + 1, minutes[i]);
    }

    return 0;
}
実行結果の例
1日目の運動時間を入力してください(分)> 30
2日目の運動時間を入力してください(分)> 45
3日目の運動時間を入力してください(分)> 20
4日目の運動時間を入力してください(分)> 60
5日目の運動時間を入力してください(分)> 35

入力された運動時間
1日目:30分
2日目:45分
3日目:20分
4日目:60分
5日目:35分
プログラムの詳しい解説

最初に、5個の整数を保存できる配列minutesを宣言しています。

int minutes[5];

この配列の有効な添字は0から4までです。宣言した直後の要素は不定値なので、最初のfor文ですべての要素へ値を入力しています。

for (int i = 0; i < 5; i++) {
    printf("%d日目の運動時間を入力してください(分)> ", i + 1);
    scanf("%d", &minutes[i]);
}

iは0から始まり、1回繰り返すたびにi++で1増えます。iが5になるとi < 5が偽になるため、ループは終了します。

2つ目のfor文では、入力済みの値を同じ順番で表示しています。

for (int i = 0; i < 5; i++) {
    printf("%d日目:%d分\n", i + 1, minutes[i]);
}

入力と表示で同じ添字の進み方を使っているため、1日目に入力した値は1日目として正しく表示されます。

図2:入力用ループと表示用ループの役割

この図から分かること

入力用のfor文は配列へ値を保存し、表示用のfor文は保存済みの値を取り出します。

処理内容は異なりますが、どちらもiを0から4まで変化させて同じ配列を順番にたどっています。配列全体を処理するときは、このように目的ごとにfor文を分けると流れが分かりやすくなります。

for文の条件はi < 要素数にする

要素数5の配列を処理するときは、次の条件が基本です。

for (int i = 0; i < 5; i++)

iの変化を確認すると、最後に処理されるのはiが4のときです。

0 → 1 → 2 → 3 → 4 → 終了

次のようにi <= 5と書いてはいけません。

for (int i = 0; i <= 5; i++) {
    printf("%d\n", minutes[i]);
}

この条件では、iが5のときもループ本体が実行されます。その結果、存在しないminutes[5]へアクセスしてしまいます。

配列の要素数正しい添字正しい条件
30~2i < 3
50~4i < 5
100~9i < 10

要素数がNなら、最後の添字はN - 1です。そのため、条件をi < Nと書けば配列の範囲内だけを処理できます。

範囲外アクセスが危険な理由

次の配列で利用できるのはminutes[0]からminutes[4]までです。

int minutes[5];

以下のアクセスは範囲外です。

minutes[5]
minutes[8]
minutes[-1]

C言語は、添字が範囲内かどうかを自動では確認してくれません。範囲外の場所へ読み書きすると、別のデータを書き換えたり、不定な値を読み出したりする可能性があります。

配列をfor文で扱うときは、次の点を確認しましょう。

  • インデックス変数を0から始めているか
  • 条件がi < 要素数になっているか
  • 配列の要素数とループ回数が一致しているか
  • 負の添字になる処理がないか

配列の要素数をマクロで管理する

要素数をコードの複数箇所へ直接書くと、変更するときに修正漏れが起こりやすくなります。

int temperatures[5];

for (int i = 0; i < 5; i++) {
    /* 処理 */
}

この5に意味のある名前を付けると、管理しやすくなります。

#define MEASURE_COUNT 5

配列の宣言とfor文の条件で、同じマクロを使用できます。

int temperatures[MEASURE_COUNT];

for (int i = 0; i < MEASURE_COUNT; i++) {
    /* 処理 */
}

測定回数を変更するときは、マクロ定義の1か所を修正するだけです。

気温を入力して合計を求めるプログラム

ファイル名:10_4_2.c

#include <stdio.h>

#define MEASURE_COUNT 5

int main(void)
{
    int temperatures[MEASURE_COUNT];
    int total = 0;

    /* 各回の気温を入力する */
    for (int i = 0; i < MEASURE_COUNT; i++) {
        printf("%d回目の気温を入力してください(℃)> ", i + 1);
        scanf("%d", &temperatures[i]);
    }

    /* 全要素を表示しながら合計する */
    printf("\n入力された気温\n");

    for (int i = 0; i < MEASURE_COUNT; i++) {
        printf("%d回目:%d℃\n", i + 1, temperatures[i]);
        total += temperatures[i];
    }

    printf("気温の合計は%dです。\n", total);

    return 0;
}
実行結果の例
1回目の気温を入力してください(℃)> 18
2回目の気温を入力してください(℃)> 21
3回目の気温を入力してください(℃)> 20
4回目の気温を入力してください(℃)> 23
5回目の気温を入力してください(℃)> 19

入力された気温
1回目:18℃
2回目:21℃
3回目:20℃
4回目:23℃
5回目:19℃
気温の合計は101です。
プログラムの詳しい解説

MEASURE_COUNTは、配列に保存する気温の個数を表しています。

#define MEASURE_COUNT 5

マクロ定義の行にはセミコロンを付けません。プログラム中のMEASURE_COUNTは、前処理によって5へ置き換えられます。

配列の要素数にもMEASURE_COUNTを使用しています。

int temperatures[MEASURE_COUNT];

入力用と計算用のfor文にも同じマクロを使っているため、配列の要素数とループ回数を合わせやすくなっています。

for (int i = 0; i < MEASURE_COUNT; i++)

合計を保存するtotalは、ループを始める前に0で初期化しています。

int total = 0;

各要素を順番に加算する処理は次の部分です。

total += temperatures[i];

これは、現在のtotalにtemperatures[i]の値を加え、結果を再びtotalへ代入しています。iが0から4まで変化するため、配列の全要素が1回ずつ合計されます。

for文で配列を処理する定番の形

配列とfor文には、よく使われる組み合わせがあります。

全要素へ入力する

for (int i = 0; i < 要素数; i++) {
    scanf("%d", &array[i]);
}

全要素を表示する

for (int i = 0; i < 要素数; i++) {
    printf("%d\n", array[i]);
}

全要素を合計する

int sum = 0;

for (int i = 0; i < 要素数; i++) {
    sum += array[i];
}

どの処理でも、配列をたどる部分は共通です。変わるのは、ループ本体で配列要素に対して何をするかです。

図3:配列処理を組み立てる3つの視点

この図から分かること

配列をfor文で処理するときは、最初に要素数を確認し、次に添字の範囲を決めます。そのうえで、入力、表示、合計などの処理をループ本体へ書きます。

要素数が5なら、添字は0から4です。この関係を保つi < 要素数という条件が、安全な配列処理の基本になります。

配列とfor文でつまずきやすいポイント

つまずきやすい書き方問題点見直し方
iを1から始める先頭の要素を処理できないことがある基本はi = 0から始める
i <= 要素数と書く最後に範囲外へアクセスするi < 要素数にする
scanfで&を付けない入力値の保存先を正しく渡せない&array[i]と書く
表示番号にもiを使う0日目や0人目と表示される表示にはi + 1を使う
要素数を何度も直書きする変更漏れが起こりやすい意味のあるマクロ名で管理する
入力前の要素を表示する不定値を使う可能性がある値を入れてから参照する

配列の添字と、人に見せる番号は同じとは限りません。内部では0から数え、表示では必要に応じて1を加える。この区別ができると、配列とfor文のコードを安定して書けるようになります。