多次元配列の扱いについて
/*Cの多次元配列の定義と初期化*/ int array[2][3] = {{1,2,3},{4,5,6}};
配列とポインタの関係
- 下記コードを実行してみる
#include <stdio.h> int main(void){ int array[2][3] = {{1,2,3},{4,5,6}}; for(int i = 0; i < 2; i++){ for(int j = 0; j < 3; j++){ printf("i:%d, j:%d, array:%d \n",i,j,array[i][j]); } } }
実行結果
i:0, j:0, array:1 i:0, j:1, array:2 i:0, j:2, array:3 i:1, j:0, array:4 i:1, j:1, array:5 i:1, j:2, array:6
i
がネスト元の配列でj
がネストされた配列のインデックスを示しているi
,j
の組み合わせで特定の配列の要素を参照している
- 上記でも説明した通り、Cでは配列の中に配列をネストさせてあくまで連番であるようなイメージで扱う
- この時、int型(4バイト)に要素数3のint型の配列(12バイト)を格納するという謎の状態が発生する
- これだとメモリが足りない為ネスト元の配列にはネストされる配列へのポインタが格納される
- int型のポインタなら4バイトなのでこれで辻褄が合う
array[2][3] = {{1,2,3},{4,5,6}}
のメモリ確保のイメージ図
[n]
はint型の1つのメモリ(4バイト)のイメージ- 我流の記法の為、こんなコードは実在しない事に注意
1.最初にarray[2]で2つの要素の配列を確保 >[][] 2.array[2]の後に[3]があるので配列の中に配列が入る多次元配列だと分かる >[[1],[2],[3]][[4],[5],[6]] 3.これだと一つの配列(array[2])に入り切らない為ポインタで退避させる >[pointerA][pointerB][1][2][3][4][5][6] 4.3の状態だとデータ参照ができない(ポインタがデータのアドレスを指していない)ため、 array[3]よりメモリがint型3つ分のデータが必要と判断し12バイト分を2つ確保する >[pointer_from_1_to_3][pointer_from_4_to_6][1][2][3][4][5][6]
- 以下の流れからCでは配列への参照はポインタを利用しているため
array[i]
と宣言すると
i
の要素分メモリを確保し先頭要素へのポインタをarray
に格納する - そのため上記の
pointer_from_a_to_c
,pointer_from_d_to_f
経由でも配列の参照は可能となる- 下記コードが多次元配列をポインタ経由で展開する代替案となる
- 2次元配列のポインタによる参照は、2重にポインタを用いる
*(array + k)
と*(array + n)
が上記のpointer_from_1_to_3
,
pointer_from_4_to_6
に当たる。これを合わせたのが*(*(array + k) + n)
となる*array
自身は配列の先頭要素のアドレスを示すポインタで、
配列は連番でメモリを確保するCの特性より+k
でずらすことで他の要素も参照できる
ただ2次元配列のため2つのポインタで配列の先頭アドレスを保持しているため2重ポインタという形になった
for (int k = 0; k < 2; k++) { for (int n = 0; n < 3; n++) { printf("k:%d, n:%d, array:%d \n", k, n, *(*(array + k) + n)); } }
- 応用例として2次元配列を1次元配列のように扱うこともできる
array_pointer[i * 3 + j]
より、添え字内の総数が全要素数になるような式にすると
全ての要素を展開できる
#include <stdio.h> int main(void){ int array[2][3] = {{1,2,3},{4,5,6}}; /*対象の配列の先頭ポインタをインデックスとして扱いたいためキャストして宣言する*/ int *array_pointer = (int *)array; for (int i = 0; i < 2; i++) { for (int j = 0; j < 3; j++) { printf("i:%d, j:%d, array_pointer:%d \n", i, j, array_pointer[i * 3 + j]); } } }
配列の先頭アドレスについて
- 配列として宣言した変数に対し
array
のように単体で記述すると、 その配列の先頭アドレスへのポインタとなる - 下記コードのように添え字が足りない配列にポインタ型以外の変数は代入できないので注意
- 多次元配列の場合は
array[1]
とすると2番目の配列の先頭ポインタを指す
- 多次元配列の場合は
int array_a[2] = { 0 }; int array_b[2][2] = { 0 }; // array_a = 1; //error array_a[0] = 1; //OK // array_b = 1; //error // array_b[1] = 1; //error array_b[0][1] = 1; //OK
まとめ
- 配列‘array‘のように添え字なしで記載するとその配列へのポインタを返す
- 配列は連番でメモリを確保する性質を利用し、多次元配列でも添え字内の
ポインタを先頭からいくつずらすという計算をすることで1次元配列っぽく扱える