C言語の基本|多次元配列の基本

行と列でデータを整理しよう。多次元配列を使えば、表のような情報を分かりやすく管理できます。

1次元配列は、複数の値を一直線に並べて管理する仕組みです。点数の一覧やセンサー値の記録など、順番に並んだデータを扱うときに役立ちます。

一方、実際のプログラムでは、縦と横の関係を持つデータもたくさん登場します。

  • 学生ごと、科目ごとの点数
  • 店舗ごと、曜日ごとの売上
  • 機械ごと、測定回ごとの温度
  • ゲーム画面のマップ
  • 画像を構成する縦横の画素

このような表形式のデータを扱うときに便利なのが多次元配列です。

多次元配列は、複数の添字を組み合わせて要素を指定します。特によく使われる2次元配列では、行と列の2つの添字を使います。

少し複雑に見えるかもしれませんが、基本的な考え方は1次元配列と同じです。1次元配列が箱を一列に並べたものなら、2次元配列は箱を縦横に並べたものと考えると分かりやすくなります。

ここでは、2次元配列を中心に、宣言、添字による要素の指定、初期化、メモリ上の並び方、二重ループを使った処理を順番に見ていきます。

多次元配列とは

多次元配列とは、複数の添字を使って要素を管理する配列です。

1次元配列では、1つの添字で要素を指定します。

data[0]
data[1]
data[2]

2次元配列では、行と列を表す2つの添字を使用します。

data[行の添字][列の添字]

たとえば、2行3列の配列は次のような表として考えられます。

行\列0列目1列目2列目
0行目data[0][0]data[0][1]data[0][2]
1行目data[1][0]data[1][1]data[1][2]

data[0][1]なら0行目の1列目、data[1][2]なら1行目の2列目を表します。

C言語の配列は添字0から始まります。人から見た1行目は添字0、2行目は添字1になることに注意しましょう。

図1:1次元配列と2次元配列の違い

この図から分かること

1次元配列は、1つの添字を使って一直線に並んだ要素を指定します。2次元配列は、行と列の2つの添字を使って表の中の要素を指定します。

どちらも同じ型の値をまとめる配列ですが、データを整理する形が異なります。行と列の関係を持つデータには、2次元配列が向いています。

多次元配列と2次元配列の関係

多次元配列は、2次元以上の配列をまとめた呼び方です。

種類添字の数データのイメージ
1次元配列1つ一列に並んだデータ
2次元配列2つ行と列を持つ表
3次元配列3つ表を複数重ねた立体的なデータ
4次元以上4つ以上さらに多くの条件を持つデータ

多次元配列の中でも、学習や実際のプログラムで特によく使われるのが2次元配列です。

まずは2次元配列を理解すれば、多次元配列に共通する添字の考え方や、繰り返し処理との組み合わせが見えてきます。

2次元配列を宣言する

2次元配列は、次の形で宣言します。

型名 配列名[行数][列数];

各部分の意味は次のとおりです。

部分意味
型名配列へ保存する値の型
配列名配列につける名前
行数縦方向に用意する要素数
列数横方向に用意する要素数

たとえば、2台の機械について3回分の温度を保存するなら、次のように宣言できます。

int temperatures[2][3];

この宣言によって、int型の要素が2行3列で用意されます。

2行 × 3列 = 6要素

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

機械\測定回1回目2回目3回目
機械1temperatures[0][0]temperatures[0][1]temperatures[0][2]
機械2temperatures[1][0]temperatures[1][1]temperatures[1][2]

この例では、行が機械、列が測定回を表しています。

ただし、行と列の意味は配列が自動的に決めるわけではありません。どの方向に何を割り当てるかは、プログラムを設計する人が決めます。

2次元配列の添字の範囲

次の配列では、行数が2、列数が3です。

int temperatures[2][3];

利用できる添字は次の範囲になります。

方向要素数使用できる添字
20~1
30~2

したがって、使用できる要素は次の6個です。

temperatures[0][0]
temperatures[0][1]
temperatures[0][2]
temperatures[1][0]
temperatures[1][1]
temperatures[1][2]

次の指定は範囲外です。

temperatures[2][0]
temperatures[0][3]
temperatures[-1][0]

temperatures[2][0]は行の範囲外、temperatures[0][3]は列の範囲外です。

C言語は範囲外アクセスを自動的に防いでくれません。1次元配列と同じように、2次元配列でも添字の範囲を正しく管理する必要があります。

2次元配列の要素に値を代入する

2次元配列の各要素には、行と列を指定して値を代入します。

temperatures[0][0] = 36;
temperatures[0][1] = 38;
temperatures[0][2] = 37;

temperatures[1][0] = 34;
temperatures[1][1] = 35;
temperatures[1][2] = 39;

temperatures[0][1]なら、0行目の1列目へ38を代入しています。

代入後の状態は次のようになります。

行\列0列目1列目2列目
0行目363837
1行目343539

値を参照するときも、同じように2つの添字を使います。

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

この場合、38と39が表示されます。

図2:行と列の添字で要素を指定する

この図から分かること

2次元配列では、最初の添字が行、2番目の添字が列を表します。

temperatures[0][1]は0行目の1列目、temperatures[1][2]は1行目の2列目です。行と列の交差する位置を指定して、目的の要素へアクセスします。

2次元配列を初期化する

2次元配列は、宣言と同時に値を入れて初期化できます。

int temperatures[2][3] = {
    {36, 38, 37},
    {34, 35, 39}
};

外側の波括弧が配列全体を表し、内側の波括弧が各行を表しています。

{36, 38, 37}

この部分が0行目に入ります。

{34, 35, 39}

この部分が1行目に入ります。

初期化子と添字の対応は次のとおりです。

初期化子入る要素
36temperatures[0][0]
38temperatures[0][1]
37temperatures[0][2]
34temperatures[1][0]
35temperatures[1][1]
39temperatures[1][2]

行ごとに内側の波括弧を分けると、表の形がコードから読み取りやすくなります。

2次元配列はメモリ上で連続して並ぶ

2次元配列は、コード上では表のように見えます。しかし、コンピュータのメモリ上に縦横の表が作られるわけではありません。

実際には、0行目の要素が順番に並び、その後ろに1行目の要素が続きます。

temperatures[0][0]
temperatures[0][1]
temperatures[0][2]
temperatures[1][0]
temperatures[1][1]
temperatures[1][2]

このように、1行分の要素を先に並べてから次の行へ進む配置を行優先と呼びます。

2行3列の配列なら、メモリ上の順序は次のようになります。

メモリ上の順番要素
1temperatures[0][0]36
2temperatures[0][1]38
3temperatures[0][2]37
4temperatures[1][0]34
5temperatures[1][1]35
6temperatures[1][2]39

この並び方に合わせて、外側のループで行、内側のループで列を処理する形がよく使われます。

2次元配列には二重ループを使う

2次元配列の全要素を処理するときは、for文を二重にします。

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

具体的には、次のような形です。

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

    printf("\n");
}

外側のfor文が行を管理し、内側のfor文が現在の行にある列を順番に処理します。

rowcolの変化処理する要素
00 → 1 → 2temperatures[0][0]~temperatures[0][2]
10 → 1 → 2temperatures[1][0]~temperatures[1][2]

rowが0の間に、colは0から2まで変化します。内側のループが終了するとrowが1になり、colは再び0から始まります。

2台の機械の温度を表示する

2台の機械について3回分の温度を表示するプログラム

ファイル名:10_7_1.c

#include <stdio.h>

int main(void)
{
    int temperatures[2][3] = {
        {36, 38, 37},
        {34, 35, 39}
    };

    printf("機械ごとの温度データを表示します。\n");

    for (int machine = 0; machine < 2; machine++) {
        printf("機械%d:", machine + 1);

        for (int measurement = 0;
             measurement < 3;
             measurement++) {
            printf("%d℃ ", temperatures[machine][measurement]);
        }

        printf("\n");
    }

    return 0;
}
実行結果の例
機械ごとの温度データを表示します。
機械1:36℃ 38℃ 37℃
機械2:34℃ 35℃ 39℃

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

配列の宣言と初期化

次の部分で、2行3列の2次元配列を用意しています。

int temperatures[2][3] = {
    {36, 38, 37},
    {34, 35, 39}
};

このプログラムでは、行が機械、列が測定回を表しています。

表すもの保存される値
0機械136、38、37
1機械234、35、39

列は測定回を表します。

表すもの
01回目
12回目
23回目

したがって、temperatures[1][2]は、機械2の3回目の温度39を表します。

外側のfor文

外側のfor文では、処理する機械を切り替えています。

for (int machine = 0; machine < 2; machine++) {

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

画面へ表示する機械番号は1から始めたほうが自然なので、machine + 1を表示しています。

printf("機械%d:", machine + 1);

配列の添字にはmachineをそのまま使用します。

内側のfor文

内側のfor文は、現在の機械について3回分の温度を順番に表示します。

for (int measurement = 0;
     measurement < 3;
     measurement++) {
    printf("%d℃ ",
           temperatures[machine][measurement]);
}

machineが0のときは、次の要素が処理されます。

temperatures[0][0]
temperatures[0][1]
temperatures[0][2]

内側のループが終了すると、外側のループによってmachineが1になります。その後、内側のループが再びmeasurement = 0から始まります。

temperatures[1][0]
temperatures[1][1]
temperatures[1][2]

この動きによって、2行3列の全6要素が1回ずつ表示されます。

改行する位置

1行分の表示が終わったところで、次の処理を実行します。

printf("\n");

この改行は内側のfor文の外側にあります。

内側のfor文の中へ書くと、温度を1つ表示するたびに改行されます。表のように1行分を横へ並べるには、列の処理がすべて終わってから改行する必要があります。

図3:二重ループが2次元配列を処理する順序

この図から分かること

二重ループでは、外側のfor文が行を管理し、内側のfor文が列を管理します。

1つの行についてすべての列を処理してから、次の行へ移ります。この処理順序は、2次元配列がメモリ上で行ごとに並ぶ行優先の順序とも一致しています。

2次元配列を扱うときの定番の形

2次元配列の全要素を表示する基本形は、次のとおりです。

for (int row = 0; row < 行数; row++) {
    for (int col = 0; col < 列数; col++) {
        printf("%d ", array[row][col]);
    }

    printf("\n");
}

各要素へ値を入力する場合も、同じ二重ループを利用できます。

for (int row = 0; row < 行数; row++) {
    for (int col = 0; col < 列数; col++) {
        scanf("%d", &array[row][col]);
    }
}

配列要素を合計する場合は、ループの中で加算します。

int total = 0;

for (int row = 0; row < 行数; row++) {
    for (int col = 0; col < 列数; col++) {
        total += array[row][col];
    }
}

処理内容は異なりますが、行と列を順番にたどる部分は共通しています。

1次元配列と2次元配列を比較する

比較項目1次元配列2次元配列
宣言例int data[6];int data[2][3];
添字の数1つ2つ
要素の指定data[i]data[row][col]
データの形一列行と列を持つ表
全要素の処理1つのfor文二重のfor文
主な用途一覧、連続データ表、マップ、行列、画像

2次元配列では添字が1つ増えますが、それぞれに役割を割り当てれば整理しやすくなります。

array[行][列]

この順番を意識し、外側のfor文で行、内側のfor文で列を処理するのが基本です。

多次元配列でつまずきやすいポイント

つまずきやすい点確認すること
行と列を逆にする最初の添字が行、2番目が列
添字を1から始めるC言語の添字は0から始まる
行数を添字に使う行数2なら最後の行添字は1
列数を添字に使う列数3なら最後の列添字は2
内側ループの条件を間違える内側は列数を条件にする
改行の位置を間違える1行分が終わった場所で改行する
宣言だけで値を使う使用前に代入または初期化する
表の見た目だけで考えるメモリ上では行ごとに連続して並ぶ

多次元配列を読むときは、配列名だけを見るのではなく、各添字が何を表しているかを確認しましょう。

たとえばtemperatures[machine][measurement]なら、machineが機械、measurementが測定回です。rowやcolだけでなく、データの意味が分かる変数名を使うと、コードの役割が伝わりやすくなります。

2次元配列は、1次元配列を行ごとに並べたものとして考えると理解しやすくなります。行と列の役割を決め、二重ループで全要素を順番に処理できるようになると、表形式のデータを自然に扱えるようになります。