C言語の基本|2次元配列の初期化

波括弧で行をそろえれば、表データの準備はすっきり完了。2次元配列の初期化を身につけよう。

2次元配列は、行と列を持つ表形式のデータを管理するための仕組みです。学生ごとの点数、店舗ごとの売上、ゲームのマップなど、縦と横の関係を持つデータを整理して保存できます。

配列を宣言しただけでは、関数内に作った各要素は不定値になることがあります。そこで役立つのが、配列の宣言と同時に最初の値を設定する初期化です。

2次元配列では、配列全体を表す波括弧の中へ、各行を表す波括弧を並べます。見た目は少し複雑ですが、表の1行ずつを順番に書いていると考えれば難しくありません。

ここでは、2次元配列を初期化する基本構文、行と列への値の対応、行数を省略できる条件、添字による参照と変更、二重for文による表示方法を順番に解説します。

2次元配列を初期化する

初期化とは、配列を宣言すると同時に最初の値を設定することです。

2次元配列は、次の形で初期化します。

型名 配列名[行数][列数] = {
    {0行目の値},
    {1行目の値},
    {2行目の値}
};

1行で表すと、次のような構文になります。

型名 配列名[行数][列数]
    = {{値, 値, ...}, {値, 値, ...}, ...};

各部分の役割は次のとおりです。

部分役割
型名各要素へ保存するデータの型
配列名2次元配列の名前
行数縦方向の要素数
列数横方向の要素数
外側の波括弧配列全体
内側の波括弧1行分の初期値

たとえば、3台の装置について朝・昼・夜の消費電力を保存するなら、次のように初期化できます。

int power[3][3] = {
    {42, 58, 49},
    {38, 61, 45},
    {47, 55, 52}
};

この配列は、3行3列の9要素を持っています。

初期化子は行ごとに対応する

2次元配列の内側の波括弧は、各行のデータを表します。

int power[3][3] = {
    {42, 58, 49},  /* 0行目 */
    {38, 61, 45},  /* 1行目 */
    {47, 55, 52}   /* 2行目 */
};

配列の内容を表にすると、次のようになります。

装置\時間帯朝・列0昼・列1夜・列2
装置1・行0425849
装置2・行1386145
装置3・行2475552

初期値と要素の対応は次のとおりです。

初期値保存される要素
42power[0][0]
58power[0][1]
49power[0][2]
38power[1][0]
61power[1][1]
45power[1][2]
47power[2][0]
55power[2][1]
52power[2][2]

内側の波括弧を行ごとに改行すると、どの値がどの行へ入るのかを確認しやすくなります。

図1:二重の波括弧と行・列の対応

この図から分かること

外側の波括弧は2次元配列全体を囲み、内側の波括弧はそれぞれ1行分のデータを表します。

最初の内側の波括弧は0行目、次は1行目、その次は2行目に対応します。それぞれの行の中では、値が列0、列1、列2の順に入ります。

行と列の添字は0から始まる

2次元配列の要素を指定するときは、行と列の2つの添字を使います。

配列名[行の添字][列の添字]

C言語の添字は0から始まります。

power[0][0]

これは、0行目の0列目、つまり装置1の朝の消費電力42を表します。

power[1][1]

こちらは、1行目の1列目、つまり装置2の昼の消費電力61を表します。

人が見る順番と添字の関係は次のようになります。

人から見た位置行の添字列の添字要素
装置1の朝00power[0][0]
装置1の夜02power[0][2]
装置2の昼11power[1][1]
装置3の朝20power[2][0]
装置3の夜22power[2][2]

3行3列の配列で利用できる添字は、行、列ともに0から2までです。

初期化した要素を変更する

初期化後も、添字を指定して個別の要素を変更できます。

たとえば、装置2の昼の消費電力を61から64へ変更するなら、次のように書きます。

power[1][1] = 64;

変更前と変更後を比較すると、次のようになります。

装置\時間帯
装置1425849
装置2・変更前386145
装置2・変更後386445
装置3475552

代入する値は、配列の要素型と同じint型として扱われます。

初期化した要素を参照する

配列の値を取り出すときも、行と列の添字を指定します。

printf("%d\n", power[2][0]);

power[2][0]には47が入っているため、次のように表示されます。

47

計算に使うこともできます。

int total = power[0][0] + power[0][1] + power[0][2];

この式では、装置1の朝・昼・夜の値を合計しています。

2次元配列全体を処理する場合は、要素を1個ずつ指定するより、二重for文を使う方法が便利です。

初期化子があれば行数を省略できる

2次元配列では、初期化子が書かれている場合に限り、先頭にある行数を省略できます。

int power[][3] = {
    {42, 58, 49},
    {38, 61, 45},
    {47, 55, 52}
};

コンパイラは、内側の波括弧が3組あることから、行数を3と判断します。

そのため、次の2つは同じ大きさの配列になります。

int power[3][3] = {
    {42, 58, 49},
    {38, 61, 45},
    {47, 55, 52}
};
int power[][3] = {
    {42, 58, 49},
    {38, 61, 45},
    {47, 55, 52}
};

どちらも3行3列の配列です。

列数は省略できない

2次元配列を初期化するとき、行数は省略できますが、列数は省略できません。

次の書き方は正しくありません。

int power[][] = {
    {42, 58, 49},
    {38, 61, 45},
    {47, 55, 52}
};

2次元配列では、1行に何個の要素があるのかが分からないと、次の行が始まる位置を決められません。そのため、列数の指定が必要です。

省略のルールを整理すると、次のようになります。

配列初期化子がある場合に省略できる部分
1次元配列要素数
2次元配列先頭の行数
3次元以上の配列最初の次元の要素数

多次元配列では、最初の次元だけを省略でき、それより内側の要素数は指定する必要があります。

図2:省略できる行数と省略できない列数

この図から分かること

初期化子があれば、コンパイラは行の数を数えられるため、2次元配列の行数を省略できます。

一方、列数はメモリ上で各行の区切りを判断するために必要です。そのため、2次元配列では列数を省略できません。

初期化した2次元配列を二重for文で処理する

2次元配列は行と列を持つため、全要素を順番に処理するときはfor文を二重にします。

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

for (行を進める処理) {
    for (列を進める処理) {
        配列[行][列]を処理する;
    }
}

3行3列の配列なら、次のように書けます。

for (int row = 0; row < 3; row++) {
    for (int col = 0; col < 3; col++) {
        printf("%d ", power[row][col]);
    }

    printf("\n");
}

外側のfor文は行を進め、内側のfor文は現在の行の列を進めます。

外側の変数row内側の変数col処理する要素
00、1、2power[0][0]~power[0][2]
10、1、2power[1][0]~power[1][2]
20、1、2power[2][0]~power[2][2]

1行分の処理が終わったあとに改行すると、配列の内容を表のように表示できます。

3台の装置の消費電力を表示する

朝・昼・夜の消費電力を装置ごとに表示するプログラム

ファイル名:10_8_1.c

#include <stdio.h>

int main(void)
{
    int power[][3] = {
        {42, 58, 49},
        {38, 61, 45},
        {47, 55, 52}
    };

    int device;
    int period;

    printf("3台の装置の消費電力を表示します。\n");

    for (device = 0; device < 3; device++) {
        printf("装置%d:", device + 1);

        for (period = 0; period < 3; period++) {
            printf("%dW ", power[device][period]);
        }

        printf("\n");
    }

    return 0;
}
実行結果の例
3台の装置の消費電力を表示します。
装置1:42W 58W 49W
装置2:38W 61W 45W
装置3:47W 55W 52W

プログラムの内容を詳しく見てみよう

行数を省略した初期化

このプログラムでは、行数を省略して配列を初期化しています。

int power[][3] = {
    {42, 58, 49},
    {38, 61, 45},
    {47, 55, 52}
};

列数は3と指定されています。内側の波括弧が3組あるため、コンパイラは行数も3と判断します。

結果として、次の宣言と同じ3行3列の配列になります。

int power[3][3];

ただし、この宣言だけでは初期値は設定されません。サンプルでは初期化子を使って9個すべての値を設定しています。

外側のfor文で装置を切り替える

外側のfor文では、処理する装置を順番に切り替えています。

for (device = 0; device < 3; device++) {

deviceは0、1、2の順に変化します。

配列の添字にはdeviceをそのまま使用しますが、画面上では装置1から表示したほうが自然です。そのため、表示にはdevice + 1を使っています。

printf("装置%d:", device + 1);

内側のfor文で時間帯を切り替える

内側のfor文では、朝・昼・夜に対応する3列を順番に処理します。

for (period = 0; period < 3; period++) {
    printf("%dW ", power[device][period]);
}

deviceが0の間は、次の3要素が表示されます。

power[0][0]
power[0][1]
power[0][2]

その後、deviceが1になり、次の3要素が表示されます。

power[1][0]
power[1][1]
power[1][2]

同じ流れで、最後に装置3のデータが表示されます。

改行する位置

改行は、内側のfor文が終了したあとに行います。

printf("\n");

この位置に改行を書くことで、1台分の朝・昼・夜が横一列に表示されます。

内側のfor文の中へ改行を書くと、値を1つ表示するたびに次の行へ移ってしまいます。

図3:初期化した配列を二重for文で表示する流れ

この図から分かること

外側のfor文は装置を表す行を進め、内側のfor文は朝・昼・夜を表す列を進めます。

1つの行にある3要素をすべて表示してから改行し、次の行へ移ります。これにより、初期化した2次元配列を表の並びに沿って自然に表示できます。

初期化と代入の違い

初期化と代入は、値を設定するタイミングが異なります。

操作タイミング
宣言配列を用意するint power[3][3];
初期化宣言と同時に値を入れるint power[3][3] = {{1, 2, 3}, ...};
代入宣言や初期化のあとで変更するpower[1][1] = 64;
参照保存された値を取り出すprintf("%d", power[1][1]);

2次元配列全体へ初期値をまとめて設定できるのは、宣言するときです。

宣言後に、次のような形で配列全体をまとめて代入することはできません。

power = {
    {42, 58, 49},
    {38, 61, 45},
    {47, 55, 52}
};

宣言後に値を変更するときは、行と列の添字を指定して、各要素へ代入します。

power[0][0] = 42;
power[1][1] = 61;
power[2][2] = 52;

2次元配列の初期化で確認したいポイント

確認項目ポイント
外側の波括弧配列全体を囲む
内側の波括弧1行分の初期値を囲む
初期値の順番行ごとに列0から順番に入る
添字の開始位置行、列ともに0から始まる
省略できる部分初期化子がある場合の行数
省略できない部分列数
全要素の処理外側を行、内側を列とする二重for文
値の変更配列名[行][列]を指定して代入する
値の参照配列名[行][列]で取り出す

2次元配列の初期化では、内側の波括弧を表の1行として考えることが大切です。行ごとに値をそろえて書けば、コードと表の対応が見やすくなります。

行数を省略するときも、列数は必ず指定します。そのうえで、外側のfor文で行、内側のfor文で列を処理すれば、初期化したすべての要素を順番に扱えるようになります。