配列

ロッカーの列を想像してください。それぞれ0から番号が振られ、同じ種類のものを収納しています——それが配列です。1つの名前で同種のデータをまとめて管理できます。

1次元配列の定義と初期化

配列は同じ型の要素がメモリ上に連続して並んだ順序付き集合です。配列を定義する際、要素の型と要素数を指定します。

C
int scores[5];

これは5つのint要素の配列を定義しますが、内容は未初期化で値は不定です。定義時に初期化することを推奨します。

C
int scores[5] = {90, 85, 78, 92, 88};

初期化リストが配列より短い場合、残りの要素は自動的に0になります。

C
int data[5] = {10, 20};

data[2]data[3]data[4]はすべて0です。この性質を利用して配列全体をゼロクリアできます。

C
int zeros[100] = {0};

完全な初期化リストを与える場合、配列の長さは省略でき、コンパイラが自動的に計算します。

C
int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

添字アクセス

配列要素は添字(インデックス)でアクセスします。添字は0から始まります。長さNの配列の有効な添字は0からN-1までです。

C
int scores[5] = {90, 85, 78, 92, 88};

int first = scores[0];
int last = scores[4];

scores[2] = 100;

添字には整数式が使えるため、ループでの走査に便利です。

C
int i = 3;
int val = scores[i];
💡 ヒント: 長さ5の配列の最大添字は4であり、5ではないことを忘れないでください。Cは実行時に配列の境界をチェックしません。境界外アクセスはエラーになりませんが、深刻な結果を招く可能性があります。

境界外アクセスの危険性

Cは配列の添字が境界外かどうかをチェックしません。境界外要素へのアクセスは未定義動作です。ゴミデータを読んだり、他の変数を書き換えたり、プログラムをクラッシュさせたりする可能性があります。

C
int arr[3] = {10, 20, 30};

printf("%d\n", arr[3]);
printf("%d\n", arr[-1]);

どちらの行も不正な位置にアクセスしています。コンパイラは警告しませんが、結果は予測不可能です。

⚠️ 注意: 境界外アクセスはCで最もよくあるバグの原因の一つです。ループを書く際は常に境界条件を確認し、長さとの比較には<=ではなく<を使いましょう。

配列の走査

forループで各要素を順番に処理するのが最も一般的な配列操作です。

C
int scores[5] = {90, 85, 78, 92, 88};
int i;

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

全要素の合計:

C
int sum = 0;
for (i = 0; i < 5; i++) {
    sum += scores[i];
}
printf("Total: %d\n", sum);

最大値を見つける:

C
int max = scores[0];
for (i = 1; i < 5; i++) {
    if (scores[i] > max) {
        max = scores[i];
    }
}
printf("Highest: %d\n", max);

平均点と不合格者数を計算します。

C
#include <stdio.h>

int main(void) {
    double scores[] = {78.5, 92.0, 55.5, 88.0, 43.0, 67.5, 90.0};
    int len = sizeof(scores) / sizeof(scores[0]);
    double sum = 0;
    int fail = 0;
    int i;

    for (i = 0; i < len; i++) {
        sum += scores[i];
        if (scores[i] < 60.0) {
            fail++;
        }
    }

    printf("Average: %.1f\n", sum / len);
    printf("Failing: %d\n", fail);

    return 0;
}
▶ 試してみよう
TEXT
Average: 73.5
Failing: 2

ここでsizeof(scores) / sizeof(scores[0])は要素数を動的に計算するため、配列の内容を変更してもループ条件を更新する必要がありません。

配列を関数の仮引数として渡す

配列を関数に渡す際、配列全体はコピーされず、最初の要素のアドレスだけが渡されます。つまり関数内でsizeofを使って配列の長さを知ることはできず、長さは別の仮引数として渡す必要があります。

C
void print_array(int arr[], int len) {
    int i;
    for (i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

仮引数int arr[]int *arrと等価で、これがポインタへの「崩れ」を反映しています。関数内で配列要素を変更すると、元の配列にも影響します。同じメモリを操作しているためです。

C
void double_values(int arr[], int len) {
    int i;
    for (i = 0; i < len; i++) {
        arr[i] *= 2;
    }
}

int main(void) {
    int data[] = {1, 2, 3, 4, 5};
    double_values(data, 5);
    return 0;
}

呼び出し後、data{2, 4, 6, 8, 10}になります。

2次元配列

2次元配列は「配列の配列」と考えられ、行列や表形式データの表現によく使われます。

C
int matrix[3][4];

これは3行4列の2次元配列を定義します。初期化は行ごとにグループ化できます。

C
int matrix[3][4] = {
    {1,  2,  3,  4},
    {5,  6,  7,  8},
    {9, 10, 11, 12}
};

フラットに書くこともできます。効果は同じです。

C
int matrix[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};

2つの添字で要素にアクセスします。

C
int val = matrix[1][2];
matrix[0][0] = 100;

2次元配列の走査には二重ループが必要です。

C
int i, j;
for (i = 0; i < 3; i++) {
    for (j = 0; j < 4; j++) {
        printf("%4d", matrix[i][j]);
    }
    printf("\n");
}

初期化時、行数は省略できますが列数は省略できません。

C
int m[][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8}
};

コンパイラは初期化データから2行であることを推定します。

3人の学生の4科目の平均点を計算します。

C
#include <stdio.h>

void student_avg(double grades[][4], int rows) {
    int i, j;
    for (i = 0; i < rows; i++) {
        double sum = 0;
        for (j = 0; j < 4; j++) {
            sum += grades[i][j];
        }
        printf("Student%d Average: %.1f\n", i + 1, sum / 4);
    }
}

int main(void) {
    double grades[3][4] = {
        {85.0, 90.0, 78.0, 92.0},
        {70.0, 65.0, 80.0, 75.0},
        {95.0, 88.0, 92.0, 97.0}
    };

    student_avg(grades, 3);

    return 0;
}
▶ 試してみよう
TEXT
Student1 Average: 86.2
Student2 Average: 72.5
Student3 Average: 93.0
💡 ヒント: 2次元配列を関数の仮引数にする場合、列数(第2次元のサイズ)は必ず指定する必要があります。行数は別の仮引数として渡します。

❓ よくある質問

Q 定義だけで初期化されていない配列にはどんな値が入っていますか?
A 局所配列は内容が不定です(スタック上のゴミ値)。大域配列とstatic配列は自動的に0で初期化されます。
Q 関数内でsizeof(arr)を使って要素数を求められないのはなぜですか?
A 配列を仮引数として渡すとポインタに崩れるため、sizeofはポインタのサイズ(4または8バイト)を返し、配列全体のサイズは返しません。
Q 配列の添字に負の数は使えますか?
A 構文的にはコンパイルされますが、配列の先頭アドレスより前のメモリにアクセスするため境界外アクセスとなり、未定義動作です。
Q 2次元配列はメモリ上にどのように格納されますか?
A 行優先順で連続的に格納されます。まず0行目の全要素、次に1行目、という順番です。

📖 まとめ

📝 練習問題

  1. 10個の整数を配列に読み込み、すべての要素を逆順に出力するプログラムを書いてください。
  2. 整数配列とその長さを受け取り、最大値の添字を返す関数を書いてください。
  3. 4x4の整数行列を定義して初期化し、両対角線の和を計算してください。
100%