C言語の2次元配列の使い方

350, 2021-11-25

目次

C言語の2次元配列の使い方

C言語では配列を扱うことができます。
この配列が2次元になったものを2次元配列と言います。
この記事ではC言語の2次元配列の使い方について詳しく解説します

2次元配列は比較的によく使われる配列の1つです。
特にゲーム開発などでは頻繁に使われることがあります。
よって2次元配列の使い方をマスターしておくのは非常に有用と言えます。

具体的には↓のコンテンツを見ていきます。

  • 2次元配列とは?

  • 2次元配列の定義方法

  • 2次元配列の参照方法

  • 2次元配列をfor分で回す

  • 2次元配列のポインタ

  • 2次元配列を関数に渡す

  • 九九の表を2次元配列で作る

2次元配列とは?

C言語における2次元配列とは、配列の次元が2つになったものを言います。
次元が2つになるということは、配列の参照に使われる添え字も2つになるということです。

matrix[y][x];  // 2次元配列の参照

2次元配列は行列とも呼ばれます。
行列は高校・大学の数学で学ぶものですが、プログラミングにおける行列(2次元配列)は数学の行列ほど複雑なものではありません。
行列的な演算を使用する場合は数学の行列の知識が必要になってきますが、そうでない場合は数学の知識は必要ありません。

# 数学で使われる行列の例

    1 0 0 0
    0 1 0 0
    0 0 1 0
    0 0 0 1

2次元配列はC言語ではゲーム開発で使われることがあります。
2Dのマップを歩くようなゲームでは高い確率で2次元配列かその類のものが使われています。
2次元配列は2Dのゲームと非常に相性が良く、効率よくロジックを定義することができます。

2次元配列はゲーム開発以外でも比較的に良く使われる配列です。
たとえばWebでは、ランキング・ページの出力に2次元配列を使ったデータ構造が使われることがあります。
ランキング・ページは24時間、そして1週間のPVを計測して表示しますが、それらのデータを2次元配列に保存しておきます。

2次元配列はかなり身近な配列です。
みなさんの身の回りでもこの配列は頻繁に使われていることだと思います。

2次元配列の定義方法

C言語では2次元配列はどのように定義するのでしょうか?
2次元配列の作り方は?
ここでは普通な2次元配列動的な2次元配列の2つの定義方法について見ていきたいと思います。

普通な2次元配列の定義方法

普通な(自動変数な)int型の2次元配列を定義する方法です。
2次元配列の定義は↓のフォーマットで行います。

型 変数名[Y次元数][X次元数] = 要素データ;

実際に定義してみましょう。
int型の3x4(Y次元3, X次元4)の2次元配列は↓のように定義します。

    int matrix[3][4] = {
        {10, 11, 12, 13},
        {20, 21, 22, 23},
        {30, 31, 32, 33},
    };

↑の2次元配列matrixはY次元の数が3, X次元の数も4な2次元配列です。
つまり行が3行で、列が4列の行列です。

    {10, 11, 12, 13},

↑の1行が2次元配列の行になります。
そして10, 11, 12, 13がそれぞれその行の列の値です。

2次元配列を定義するとき、Y次元の要素数は省略することが可能です。

    int matrix2[][4] = {
        {10, 11, 12, 13},
        {20, 21, 22, 23},
        {30, 31, 32, 33},
    };

↑の場合はY次元の要素数を省略していますが、定義内容によって動的にY次元の次元数が変化します。
ちなみにX次元の要素数は省略することはできません。
コンパイルエラーになります。

定義ではなく2次元配列を宣言したい場合は↓のように書きます。

    int matrix3[4][4];

↑のように宣言する場合は要素数は省略できません。
また宣言のみの2次元配列は初期化されていないので、要素の値にはでたらめな値が入る可能性があります。

動的な2次元配列の定義方法

動的なメモリ確保で2次元配列を定義する場合は↓のようにします。

#include <stdlib.h>

int main(void) {
    // 動的な2次元配列の確保
    int nrows = 3;  // Y次元の要素数
    int ncols = 4;  // X次元の要素数
    int **matrix = calloc(nrows, sizeof(int *));

    for (int y = 0; y < nrows; y += 1) {
        matrix[y] = calloc(ncols, sizeof(int));
    }

    // 動的な2次元配列の解放
    for (int y = 0; y < nrows; y += 1) {
        free(matrix[y]);
    }
    free(matrix);

    return 0;
}

動的にメモリを確保して2次元配列を作る場合は↑のようにちょっとめんどくさいです。
まずnrowsncolsに2次元配列の次元数を保存します。
nrowsがY次元、ncolsがX次元です。

そしてmatrixというダブルポインタの変数にcalloc()でメモリを確保してそのメモリを保存します。
calloc()は第1引数に要素数、第2引数に要素のバイト数を取ります。

そのあとにnrowsfor文で回してmatrix[y]calloc()でメモリを入れます。
このmatrix[y]は2次元配列の行に相当する部分です。
行には列数(ncols)の数だけ要素を確保するので、calloc()にもそのように指定します。

動的に確保した2次元配列はメモリの解放を行う必要があります。
その場合は最初に行(matrix[y])のメモリをすべて解放します。
それからmatrix本体を解放します。
こうすればメモリを解放できます。

大きな2次元配列を作りたい場合、あるいは取り扱いが楽な2次元配列を作りたい場合は、動的なメモリ確保を行うと良いでしょう
calloc()malloc()はヒープ領域からメモリを仕入れてくるので、メモリが許す限り大きな行列を作れます。

2次元配列の参照方法

2次元配列はどのように参照すればいいのでしょうか?
2次元配列の要素をprintf()するには?
これは添え字を使って参照します。
具体的に見ていきたいと思います。

添え字による参照

2次元配列を参照するには1次元配列と同じく添え字で参照します

#include <stdio.h>

int main(void) {
    int matrix[3][4] = {
        {10, 11, 12, 13},
        {20, 21, 22, 23},
        {30, 31, 32, 33},
    };

    printf("%d\n", matrix[0][0]);  // 10
    printf("%d\n", matrix[0][1]);  // 11
    printf("%d\n", matrix[0][2]);  // 12
    printf("%d\n", matrix[0][3]);  // 13

    printf("%d\n", matrix[1][0]);  // 20
    printf("%d\n", matrix[1][1]);  // 21
    printf("%d\n", matrix[1][2]);  // 22
    printf("%d\n", matrix[1][3]);  // 23

    printf("%d\n", matrix[2][0]);  // 30
    printf("%d\n", matrix[2][1]);  // 31
    printf("%d\n", matrix[2][2]);  // 32
    printf("%d\n", matrix[2][3]);  // 33

    return 0;
}

2次元配列の添え字による参照は↓のようなフォーマットで行います。

matrix[Y次元の添え字][X次元の添え字]

つまり「0行目の2列目」という参照をしたい場合は↓のようになります。

    printf("%d\n", matrix[0][2]);  // 12

範囲外の添え字による参照はセグフォになる可能性があり、プログラムがクラッシュするかもしれません。
範囲外の添え字で参照しないように気をつけてください。

上記の参照方法はマジックナンバーによる参照ですが、もちろん変数を使った参照も可能です。

#include <stdio.h>

int main(void) {
    int matrix[3][4] = {
        {10, 11, 12, 13},
        {20, 21, 22, 23},
        {30, 31, 32, 33},
    };

    int y = 1;
    int x = 2;
    printf("%d\n", matrix[y][x]);  // 22

    return 0;
}

sizeofで要素数を取得

sizeof演算子はオペランドのバイト数を得る演算子です。
これを利用して2次元配列の行数と列数を得ることができます

#include <stdio.h>

int main(void) {
    int matrix[3][4] = {
        {10, 11, 12, 13},
        {20, 21, 22, 23},
        {30, 31, 32, 33},
    };

    printf("行数: %d\n", sizeof(matrix) / sizeof(matrix[0]));
    // 行数: 3

    printf("列数: %d\n", sizeof(matrix[0]) / sizeof(matrix[0][0]));
    // 列数: 4

    return 0;
}

matrix[0]と参照すると、matrixの0行目の配列を得ることができます。
この配列全体のサイズをsizeofで求めます。
そして2次元配列matrix全体のサイズをsizeofで求めて、その数に割ります。
これで行数を得ることができます。

sizeof(matrix) / sizeof(matrix[0]);  // matrixの行数を得る

matrix[0][0]と参照すると、matrixの0行目、0列目の要素を得ることができます。
この要素のサイズをsizeofで求めます。
そしてmatrix[0]のサイズにその数を割ると、列数が求まります。

sizeof(matrix[0]) / sizeof(matrix[0][0]);  // matrix[0]の列数を得る

このsizeofを使った行数/列数を求める方法は、動的な2次元配列やポインタには使うことができません
動的な2次元配列やポインタにこのsizeofを使った場合は意図しない結果になりますので注意が必要です

2次元配列をfor分で回す

2次元配列をfor文で回す方法です
↓のようにコードを書きます。

#include <stdio.h>

int main(void) {
    int matrix[3][4] = {
        {10, 11, 12, 13},
        {20, 21, 22, 23},
        {30, 31, 32, 33},
    };

    for (int y = 0; y < 3; y += 1) {
        for (int x = 0; x < 4; x += 1) {
            printf("%d ", matrix[y][x]);
        }
        printf("\n");
    }
    // 10 11 12 13
    // 20 21 22 23
    // 30 31 32 33

    return 0;
}

2次元配列の要素を出力したい場合は↑のようにfor文を入れ子にします。
つまり2次元配列なのでfor文も2つ必要になるということです。
for文1つで回すこともやろうと思えばできます)

外側for文は行に対応します
内側for文は列に対応します

外側のfor文ではカウント変数yをカウントし、内側のfor文ではカウント変数xをカウントします。
そしてそれらの添え字を使ってmatrix[y][x]とやって参照します。

for文の中にあるprintf()は2次元配列の要素を出力します。
そして内側のfor文が終了したらprintf()で改行を1つ入れます。
このように出力することで2次元配列の要素を行列のように出力できます。

2次元配列のポインタ

動的でない2次元配列はダブルポインタの変数に代入することはできません

#include <stdio.h>

int main(void) {
    int matrix[3][4] = {
        {10, 11, 12, 13},
        {20, 21, 22, 23},
        {30, 31, 32, 33},
    };

    int **pp = matrix;
    // warning: initialization of ‘int **’ from incompatible pointer type ‘int (*)[4]’

    printf("%d\n", pp[0][0]);
    // Segmentation fault

    return 0;
}

↑のようにダブルポインタに代入するとコンパイラから警告が出力されます。
そして要素を参照しようとするとセグフォになります。

2次元配列をポインタに入れたい場合はシングルなポインタに代入します。

#include <stdio.h>

int main(void) {
    int matrix[3][4] = {
        {10, 11, 12, 13},
        {20, 21, 22, 23},
        {30, 31, 32, 33},
    };

    int *p = (int *) matrix;

    printf("%d\n", *(p + (0 * 4) + 1));  // 11
    printf("%d\n", *(p + (1 * 4) + 2));  // 22

    return 0;
}

2次元配列を1次元のポインタにしたわけですが、そうなると添え字の参照方法も↑のように独特な記法になります。
↓をズームアップして見てみましょう。

    printf("%d\n", *(p + (0 * 4) + 1));  // 11

*(p + (0 * 4) + 1)(0 * 4)は行を表しています。
↑の2次元配列は3x4の2次元配列なので、列数は4です。そのため(行番号 * 列数)で1次元配列上の行の位置を取得できます。
(0 * 4) + 1+ 1は列を表しています。
先ほど取得した行の位置から+ 1ということはその行の1列目(0オリジン)の要素と言う意味になります。

この1次元配列の参照方法はゲームなどでは使われることがありますが、ご覧の通りあまり直感的ではありません
そのためあまり使われません。
2次元配列を持ち運びたい場合は構造体でラップするといいでしょう。

あるいは動的なメモリ確保で2次元配列を確保しておくと良いかもしれません。
その場合はダブルポインタに保存できます。

2次元配列を関数に渡す

2次元配列を関数に渡したい場合は↓のようにコードを書きます。

#include <stdio.h>

void func(int matrix[3][4]) {
    printf("%d\n", matrix[0][1]);  // 11
    printf("%d\n", matrix[1][2]);  // 22
}

int main(void) {
    int matrix[3][4] = {
        {10, 11, 12, 13},
        {20, 21, 22, 23},
        {30, 31, 32, 33},
    };

    func(matrix);

    return 0;
}

関数の引数に2次元配列を書く場合は↑のように行数と列数を書きます
行数は例によって省略することができます
↑のfuncの引数matrixは普通の2次元配列に見えますが、その実体はただのポインタです
そのためsizeofで要素数を求める演算は機能しませんので注意してください。

九九の表を2次元配列で作る

2次元配列で九九の表を作ってみます。

#include <stdio.h>

int main(void) {
    // 九九の表を作成
    int kuku[9][9];

    for (int y = 0; y < 9; y += 1) {
        for (int x = 0; x < 9; x += 1) {
            kuku[y][x] = (y + 1) * (x + 1);
        }
    }

    // 九九の表を出力
    for (int y = 0; y < 9; y += 1) {
        for (int x = 0; x < 9; x += 1) {
            printf("%2d ", kuku[y][x]);
        }
        printf("\n");
    }

    return 0;
}

kukuという2次元配列に九九の演算結果を保存します。
↑の例で言うと

    kuku[y][x] = (y + 1) * (x + 1);

というのが演算部分です。
yxはともに0から9より下まで回ります。
それに1を足して互いに掛けると九九の表の要素の値が求まります。

あとはfor文の入れ子でkukuを出力します。
結果は↓になります。

 1  2  3  4  5  6  7  8  9
 2  4  6  8 10 12 14 16 18
 3  6  9 12 15 18 21 24 27
 4  8 12 16 20 24 28 32 36
 5 10 15 20 25 30 35 40 45
 6 12 18 24 30 36 42 48 54
 7 14 21 28 35 42 49 56 63
 8 16 24 32 40 48 56 64 72
 9 18 27 36 45 54 63 72 81

おわりに

今回はC言語の2次元配列について具体的に解説しました。
2次元配列は普通の配列と同様によく使われる配列です。
2次元配列の使い方をマスターしておけば色々なシーンで役に立ちます。

2次元配列を制する者は

2Dを制す