C++ / 多次元配列を使う場合

配列の内部表現

C++ で配列を定義した場合,メモリ上には以下のように配置される.

int a[3] = {1 ,2 ,3};

多次元配列でもメモリ上に連続的に配置される.

int a[2][3] =
{
    {1 ,2 ,3},
    {4 ,5 ,6},
};

引数で渡す場合

関数の引数が1次元配列をとる場合,以下のように書くことができた.

func(int a[]) または func(int *a)

引数に配列型の変数を渡した場合,その配列の先頭のポインタが渡される.
各要素は int 型(幅が sizeof(int) バイト)という情報がわかれば,[]演算でその要素があるメモリの位置を特定することができる.
a[3] であれば,sizeof(int) * 2 バイト目を見ると,3個目の要素がある.

なので,2次元の場合も以下のように書けそうではあるが,これはコンパイルエラーである.

func(int a[][]) または func(int **a)

a[2][3] は各要素が「大きさが3であるint型配列」の配列であるが,引数に配列を渡した場合,渡されるのは配列の先頭のポインタである.
今回はa[1][2]としても各要素が「大きさが3であるint型配列」という情報は,関数側では知りようがないので,位置を特定できなくなってしまうのである.

なので次のようにすればいよい.

void func(int a[][3], int m)
{
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < 3; j++) {
            std::cout << a[i][j] << ", ";
        }
        std::cout << std::endl;
    }
}

こうすれば,各要素は「要素数が3であるint型配列」という情報がわかるので,位置を特定できる.

もしくは2次元配列がメモリ上に以下のように配置されることを利用すると,a[i][j] のアドレスは(i * 3 + j) * sizeof(int)と計算できる.

void func(int *a, int m, int n)
{
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            std::cout << a[i * n + j] << ", ";
        }
        std::cout << std::endl;
    }
}

2つの書き方を比較すると,前者は関数内で配列を参照するとき添字演算子[]が使えるが,引数に外側の配列の次元数を入れなければならない.

static const n = 3 または #define n 3
void func(int a[][n], int m)

とすれば,多少マシにはなるが,外側の次元数が決まった配列しか渡せないので,汎用性は下がる.
後者は配列の要素にアクセスがa[i * n + j]と少しややこしくなるが,任意の2次元配列を渡すことができる.
ちなみに速度はどちらも変わらない.a[i][j]でもa[i * n + j]でも結果的には,(i * n + j) * sizeof(int)としてメモリ上の要素の位置が計算されるからである.

コメントを残す

メールアドレスが公開されることはありません。