C言語基礎|多次元配列

1次元配列は「同じ型のデータを横一列に並べたもの」でしたよね。
じゃあ、その配列をさらに並べたら?――そう、配列の配列になります。これが 多次元配列です。

  • 2次元配列:行と列で表せる(表・マス目)
  • 3次元配列:2次元配列がさらに積み重なる(ページ・回数・層)
  • それ以上:理屈は同じ(ただし使いどころは慎重に)

ここでは「2次元配列の考え方」と「C言語での並び方(メモリの連続配置)」を、図と表でしっかり固めます。

多次元配列って結局なに?

多次元配列は、ひとことで言うとこうです。

多次元配列は、配列を要素とする配列である。

1次元配列との違いを並べると分かりやすいです。

種類要素の型イメージ
1次元配列int a[3];int1列に並ぶ
2次元配列int m[4][3];int[3]4行×3列の表
3次元配列int t[15][4][3];int[4][3]15枚の表(回数×行×列)

ポイントは 2次元配列の要素は1次元配列、ってところです。

2次元配列ができるまで(派生の考え方)

2次元配列は、2段階でできると考えるとスッキリします。

段階何をまとめる?できる型
a → bint を 3個まとめるint[3]
b → cint[3] を 4個まとめるint[4][3]

図のイメージ

C言語の2次元配列宣言は、右から読むクセをつけると楽です。

宣言の読み方:int a[4][3]; は「4行3列」

宣言行数(先頭側)列数(末尾側)表のサイズ
int a[4][3];434行3列
int a[3][4];343行4列

アクセス方法:a[i][j] は [ ] を2回使う

2次元配列の「1マス」を触るには、添字演算子を連続で使います。

a[i][j]

  • a[i] で「i行目(1次元配列)」を選ぶ
  • その中の [j] で「j列目(int)」を選ぶ

構成要素(いちばん小さい要素=int)まで分解すると、
4行3列なら 4×3 = 12個の構成要素があります。

メモリ上の並び:末尾側の添字が先に増える(超重要)

2次元配列は「表っぽい」見た目ですが、メモリ上では 一直線に連続して並びます。
しかも並び順はこうです。

末尾側の添字(列)が 0,1,2… と先に増えてから、先頭側(行)が増える

int a[4][3] の並びイメージ

a[0][0] a[0][1] a[0][2]  a[1][0] a[1][1] a[1][2]  ...  a[3][2]

つまり、a[0][2] の次が a[1][0] になるのが保証されます。
この並びを「行優先(row-major)」と呼ぶこともあります。

サンプルプログラム

ここでは 3人分の7日間の歩数を 2次元配列で持ち、人ごとの合計曜日ごとの合計を出すプログラムを例に解説します。

プロジェクト名:chap5-9-1 ソースファイル名:chap5-9-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

// 3人×7日間の歩数データを集計して表示
#include <stdio.h>

#define PEOPLE 3
#define DAYS 7

int main(void)
{
    int steps[PEOPLE][DAYS] = {
        {8021, 7560, 9102, 10034, 6780, 12010, 8450},
        {6500, 7200, 7001, 6900, 8800, 9100, 5000},
        {11000, 10500, 9800, 10200, 9700, 9900, 10100}
    };

    int person_sum[PEOPLE] = {0};
    int day_sum[DAYS] = {0};

    // 集計(2重ループで全構成要素を走査)
    for (int i = 0; i < PEOPLE; i++) {
        for (int j = 0; j < DAYS; j++) {
            person_sum[i] += steps[i][j];
            day_sum[j] += steps[i][j];
        }
    }

    puts("=== 3人×7日間の歩数一覧 ===");
    for (int i = 0; i < PEOPLE; i++) {
        printf("Person %d:", i + 1);
        for (int j = 0; j < DAYS; j++)
            printf(" %5d", steps[i][j]);
        putchar('\n');
    }

    puts("\n--- 人ごとの合計 ---");
    for (int i = 0; i < PEOPLE; i++)
        printf("Person %d total = %d\n", i + 1, person_sum[i]);

    puts("\n--- 曜日ごとの合計 ---");
    for (int j = 0; j < DAYS; j++)
        printf("Day %d total = %d\n", j + 1, day_sum[j]);

    return 0;
}

この例の気持ちいいところは、steps[i][j] を1回読むだけで、2種類の集計(人・曜日)が同時に進むことです。
2重ループに慣れると、表データの処理が一気に得意になります。

行と列に「意味」を割り当てる

2次元配列は、行と列に役割を持たせると一気に読みやすくなります。

添字何を表す?
i(行)人(Person)i=2 は3人目
j(列)日(Day)j=5 は6日目
steps[i][j]
  i = person index (row)
  j = day index    (column)

3次元配列への拡張イメージ(回数が増えたら)

もし「7日間」を「12週間」や「15回測定」に増やしたら、
2次元を何枚も持つより、3次元にまとめたくなります。

例:

  • 15回分の「3人×7日」の歩数
    int steps[15][3][7];

アクセスは steps[k][i][j] のように [ ] が3回になります。

登場する命令・演算子の書式と役割

配列宣言(2次元)

書式

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

何をする?
「列数の1次元配列」を行数個まとめて、2次元配列を作ります。

初期化(2次元)

書式

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

何をする?
行ごとに { } を分けて初期化できます(見た目が表っぽくなって読みやすいです)。

for 文(2重ループ)

書式

for (i = 0; i < 行数; i++) {
    for (j = 0; j < 列数; j++) {
        処理;
    }
}

何をする?
表の全マス(全構成要素)を順番に走査して処理します。

puts / printf / putchar

  • puts(文字列); は1行表示(改行つき)
  • printf(書式, 値...); は整形表示
  • putchar('\n'); は改行など1文字出力

演習問題

演習5-10

3行4列の2次元配列をキーボードから読み込み、同じ形で表示せよ。

解答例

プロジェクト名:chap5-9-2 ソースファイル名:chap5-9-2.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>

int main(void)
{
    int a[3][4];

    puts("3行4列の整数を入力してください:");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            printf("a[%d][%d]:", i, j);
            scanf("%d", &a[i][j]);
        }
    }

    puts("\n入力された表:");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++)
            printf("%4d", a[i][j]);
        putchar('\n');
    }

    return 0;
}

解説
2次元配列の基本は 読み込みも表示も2重ループ
まずは「全マスをなぞる」感覚を身体で覚えるのが狙いです。

演習5-11

4人×3科目の点数を読み込み、学生ごとの合計点だけを表示せよ。

解答例

プロジェクト名:chap5-9-3 ソースファイル名:chap5-9-3.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>

#define STU 4
#define SUB 3

int main(void)
{
    int score[STU][SUB];
    int sum[STU] = {0};

    puts("4人×3科目の点数を入力してください:");
    for (int i = 0; i < STU; i++) {
        printf("Student %d:\n", i + 1);
        for (int j = 0; j < SUB; j++) {
            printf("  Subject %d:", j + 1);
            scanf("%d", &score[i][j]);
            sum[i] += score[i][j];
        }
    }

    puts("\n--- 学生ごとの合計点 ---");
    for (int i = 0; i < STU; i++)
        printf("Student %d total = %d\n", i + 1, sum[i]);

    return 0;
}

解説
「表(学生×科目)」から、行方向(学生方向)に足し込む練習です。
2次元配列を使う理由が一気にハッキリします。

演習5-12

2回分の点数(回×学生×科目)を3次元配列に読み込み、回ごとの学生合計点を表示せよ。

解答例

プロジェクト名:chap5-9-4 ソースファイル名:chap5-9-4.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>

#define TIMES 2
#define STU 3
#define SUB 2

int main(void)
{
    int score[TIMES][STU][SUB];
    int sum[TIMES][STU] = {0};

    puts("2回分の点数を入力してください(3人×2科目):");
    for (int t = 0; t < TIMES; t++) {
        printf("\nRound %d\n", t + 1);
        for (int i = 0; i < STU; i++) {
            printf(" Student %d:\n", i + 1);
            for (int j = 0; j < SUB; j++) {
                printf("  Subject %d:", j + 1);
                scanf("%d", &score[t][i][j]);
                sum[t][i] += score[t][i][j];
            }
        }
    }

    puts("\n--- 回ごとの学生合計点 ---");
    for (int t = 0; t < TIMES; t++) {
        printf("Round %d:\n", t + 1);
        for (int i = 0; i < STU; i++)
            printf("  Student %d total = %d\n", i + 1, sum[t][i]);
    }

    return 0;
}

解説
3次元配列も怖くないです。
添字が1つ増えただけで、やってることは同じ。

  • t:回
  • i:学生(行)
  • j:科目(列)

for が1段増えるだけ、という感覚を掴めると勝ちです。