[前へ]   [目次へ]   [次へ]

多次元配列(C/C++)


今回は、以前からしばしば言葉だけ出てきた多次元配列についてです。

多次元配列とは、簡単に言ってしまうと「配列の配列」です。
扱い方としても「配列」の要素型がまた「配列」というだけなのですが、
型変化がちょっと表記上ややこしいので注意です。

多次元配列を作成するには、要素数を複数つなげた配列として定義します。

例: char 型の要素数10の配列を20個持つ2次元配列「 array 」を作成する
char array[20][10];

単体型の要素数が後ろに来る点に注意してください。
逆に書いてしまうと領域違反の原因になります。

上記の例で定義した array の型は char[20][10] と表現されます。
通常の配列型同様、式中では暗黙変換が発生します。
基本的な式パターンでの変化は以下のようになります。(-となっているものは暗黙変換しない)

式自体の型暗黙変換後の型
arraychar[20][10]char(*)[10]
array[0]char(&)[10]char*
array[0][0]char&charの暗黙変換と同じ
&arraychar(*)[20][10]-
*arraychar(&)[10]char*
&*arraychar(*)[10]-


ところで、紛らわしいのですが、 char*[10] という型と char(*)[10] という型は意味が違います。
char*[10] は「 char* の要素数10の配列」であり、
char(*)[10] は「 char[10] へのポインタ」です。
また、 char(&)[10] は「 char[10] への参照」として有効ですが、 char&[10] という型はありません。

各種型宣言/定義文
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
< 15>
< 16>
< 17>
< 18>
< 19>
< 20>
< 21>
#include <stdio.h>

int main(void){
   
int a[3][5]={{10,11,12,13,14},{20,21,22,23,24},{30,31,32,33,34}};//int[3][5]
   
int *b[5]={NULL,NULL,NULL,NULL,NULL};//int*[5]を全てNULLで初期化しておきます
   
int (*c)[5]=NULL;//int(*)[5]をNULLで初期化します
   //int &d[5];//コンパイルエラー「参照の配列は作れません」
   
int (&e)[5]=a[0];//int(&)[5] a[0]の別名としてeを作成します。(C++のみ)
   
   
printf("%d\n",e[3]);//(1)eはa[0]の別名であり、同じものを示します。
   //b[0]=a;//(2)コンパイルエラー「int[3][5]からint*へ変換できません」
   
b[0]=a[0];//(3)b[0]にa[0]の絶対ID(ポインタ)を代入
   
printf("%d\n",b[0][3]);//(4)b[0][3]はa[0][3]になります
   
c=a;//(5)cにaの絶対ID(ポインタ)を代入
   
printf("%d\n",c[0][3]);//(6)c[0][3]はa[0][3]になります
   //printf("%d\n",b[1][3]);//(7)これはアクセス違反になります
   
printf("%d\n",c[1][3]);//(8)c[1][3]はa[1][3]になります
   //終了待ち
   
getchar();
   
return 0;
実行結果:
13
13
13
23


いろいろ紛らわしいので1行づつ見ていきます。
今回は(1)10行目の直前からスタートです。

この時点までを実行した時の状況の一例
実体ID(絶対)実体の型保持値所属
0x0012FF44int[3][5]main(a,line 4)
0x0012FF44int[5]main(a[0],line 4)
0x0012FF44int10main(a[0][0],line 4)
0x0012FF48int11main(a[0][1],line 4)
0x0012FF4Cint12main(a[0][2],line 4)
0x0012FF50int13main(a[0][3],line 4)
0x0012FF54int14main(a[0][4],line 4)
0x0012FF58int[5]main(a[1],line 4)
0x0012FF58int20main(a[1][0],line 4)
0x0012FF5Cint21main(a[1][1],line 4)
0x0012FF60int22main(a[1][2],line 4)
0x0012FF64int23main(a[1][3],line 4)
0x0012FF68int24main(a[1][4],line 4)
0x0012FF6Cint[5]main(a[2],line 4)
0x0012FF6Cint30main(a[2][0],line 4)
0x0012FF70int31main(a[2][1],line 4)
0x0012FF74int32main(a[2][2],line 4)
0x0012FF78int33main(a[2][3],line 4)
0x0012FF7Cint34main(a[2][4],line 4)
0x0012FF30int*[5]main(b,line 5)
0x0012FF30int*NULLmain(b[0],line 5)
0x0012FF34int*NULLmain(b[1],line 5)
0x0012FF38int*NULLmain(b[2],line 5)
0x0012FF3Cint*NULLmain(b[3],line 5)
0x0012FF40int*NULLmain(b[4],line 5)
0x0012FF2Cint(*)[5]NULLmain(c,line 6)
0x0012FF28int(&)[5]0x0012FF44(main::a[0])main(e,line 8)
mainブロック 確保位置ID:0x0012FF28

まず、(1)10行目からです。

A      B C        D E F E' B'
printf ( "%d\n" , e [ 3 ]  )


通し記号種別
Aint(*)(const char*,...)0x00401470関数(printf)
B演算子( () )(その他、優先16、結合→)
Cconst char[4]"%d\n"文字列定数
Dint(&)[5]参照(参照先:a[0](0x0012FF44))
E演算子( [] )(その他、優先16、結合→)
Fconst int3定数
E'演算子Eの終点
B'演算子Bの終点
優先順位および結合規則から演算子Bからです。
しかし、D〜E'が式なのでこちらから解決です。

D E F E'
e [ 3 ]


通し記号種別
Dint(&)[5]参照(参照先:a[0](0x0012FF44))
E演算子( [] )(その他、優先16、結合→)
Fconst int3定数
E'演算子Eの終点
演算子E(配列添字参照、左辺 参照D、座標 定数F)を処理しますが、
左辺が「配列への参照」のため暗黙的型キャストが発生し、
参照先へのポインタ型へキャストされます。

D      E F E'
vtemp1 [ 3 ]


通し記号種別
Dint*0x0012FF44(a[0][0])一時変数
E演算子( [] )(その他、優先16、結合→)
Fconst int3定数
E'演算子Eの終点
いつものように、 0x0012FF44(一時変数D) + 4(一時変数Dの示す型intの大きさ) * 3(定数F) を計算し、
0x0012FF50 になり、上表より a[0][3] への参照が作られます。


A      B C        D      B'
printf ( "%d\n" , vtemp2 )


通し記号種別
Aint(*)(const char*,...)0x00401470関数(printf)
B演算子( () )(その他、優先16、結合→)
Cconst char[4]"%d\n"文字列定数
Dint&13一時変数(参照先:a[0][3](0x0012FF50))
B'演算子Bの終点
これで、一回目の13が出力されます。



続いて、(3)12行目です。

A B C B' D E F G F'
b [ 0 ]  = a [ 0 ]


通し記号種別
Aint*[5]変数(b)
B演算子( [] )(その他、優先16、結合→)
Cconst int0定数
B'演算子Bの終点
D演算子(=)(2項、算術、優先2、結合←)
Eint[3][5]変数(a)
F演算子( [] )(その他、優先16、結合→)
Gconst int0定数
F'演算子Fの終点
優先順位および結合規則から演算子Bからです。
演算子B(配列添字参照、左辺 変数A、座標 定数C)を処理しますが、
左辺が「配列」のため暗黙的型キャストが発生し、
参照先へのポインタ型へキャストされます。

A      B C B' D E F G F'
vtemp1 [ 0 ]  = a [ 0 ]


通し記号種別
Aint**0x0012FF30(b[0])一時変数
B演算子( [] )(その他、優先16、結合→)
Cconst int0定数
B'演算子Bの終点
D演算子(=)(2項、算術、優先2、結合←)
Eint[3][5]変数(a)
F演算子( [] )(その他、優先16、結合→)
Gconst int0定数
F'演算子Fの終点
二段のポインタになっていますが、特に処理は変わりません。
いつもどおり、 0x0012FF30(一時変数A) + 4(一時変数Aの示す型int*の大きさ) * 0(定数C) を計算し、
0x0012FF30 になり、上表より b[0] への参照が作られます。

A      B C D E D'
vtemp2 = a [ 0 ]


通し記号種別
Aint*&0x00000000(NULL)一時変数(参照先:b[0](0x0012FF30))
B演算子(=)(2項、算術、優先2、結合←)
Cint[3][5]変数(a)
D演算子( [] )(その他、優先16、結合→)
Econst int0定数
D'演算子Fの終点
続いて、演算子D(配列添字参照、左辺 変数C、座標 定数E)を処理しますが、
また左辺が「配列」のため暗黙的型キャストが発生し、
参照先へのポインタ型へキャストされます。

A      B C      D E D'
vtemp2 = vtemp3 [ 0 ]


通し記号種別
Aint*&0x00000000(NULL)一時変数(参照先:b[0](0x0012FF30))
B演算子(=)(2項、算術、優先2、結合←)
Cint(*)[5]0x0012FF44(a[0])一時変数
D演算子( [] )(その他、優先16、結合→)
Econst int0定数
D'演算子Fの終点
配列へのポインタになっていますが、特に処理は変わりません。
いつもどおり、 0x0012FF44(一時変数C) + 20(一時変数Cの示す型int[5]の大きさ) * 0(定数E) を計算し、
0x0012FF44 になり、上表より a[0] への参照が作られます。

A      B C
vtemp2 = vtemp4


通し記号種別
Aint*&0x00000000(NULL)一時変数(参照先:b[0](0x0012FF30))
B演算子(=)(2項、算術、優先2、結合←)
Cint(&)[5]一時変数(参照先:a[0](0x0012FF44))
一時変数Cをさらに暗黙変換します。

A      B C
vtemp2 = vtemp5


通し記号種別
Aint*&0x00000000(NULL)一時変数(参照先:b[0](0x0012FF30))
B演算子(=)(2項、算術、優先2、結合←)
Cint*0x0012FF44(a[0][0])一時変数
これで b[0] に a[0] の先頭要素への絶対IDが代入されます。

この時点までを実行した時の状況の一例
実体ID(絶対)実体の型保持値所属
0x0012FF44int[3][5]main(a,line 4)
0x0012FF44int[5]main(a[0],line 4)
0x0012FF44int10main(a[0][0],line 4)
0x0012FF48int11main(a[0][1],line 4)
0x0012FF4Cint12main(a[0][2],line 4)
0x0012FF50int13main(a[0][3],line 4)
0x0012FF54int14main(a[0][4],line 4)
0x0012FF58int[5]main(a[1],line 4)
0x0012FF58int20main(a[1][0],line 4)
0x0012FF5Cint21main(a[1][1],line 4)
0x0012FF60int22main(a[1][2],line 4)
0x0012FF64int23main(a[1][3],line 4)
0x0012FF68int24main(a[1][4],line 4)
0x0012FF6Cint[5]main(a[2],line 4)
0x0012FF6Cint30main(a[2][0],line 4)
0x0012FF70int31main(a[2][1],line 4)
0x0012FF74int32main(a[2][2],line 4)
0x0012FF78int33main(a[2][3],line 4)
0x0012FF7Cint34main(a[2][4],line 4)
0x0012FF30int*[5]main(b,line 5)
0x0012FF30int*0x0012FF44(main::a[0][0])main(b[0],line 5)
0x0012FF34int*NULLmain(b[1],line 5)
0x0012FF38int*NULLmain(b[2],line 5)
0x0012FF3Cint*NULLmain(b[3],line 5)
0x0012FF40int*NULLmain(b[4],line 5)
0x0012FF2Cint(*)[5]NULLmain(c,line 6)
0x0012FF28int(&)[5]0x0012FF44(main::a[0])main(e,line 8)
mainブロック 確保位置ID:0x0012FF28


続いて、(4)13行です。

A      B C        D E F E' G H G' B'
printf ( "%d\n" , b [ 0 ]  [ 3 ]  )


通し記号種別
Aint(*)(const char*,...)0x00401470関数(printf)
B演算子( () )(その他、優先16、結合→)
Cconst char[4]"%d\n"文字列定数
Dint*[5]変数(b)
E演算子( [] )(その他、優先16、結合→)
Fconst int0定数
E'演算子Eの終点
G演算子( [] )(その他、優先16、結合→)
Hconst int3定数
G'演算子Gの終点
B'演算子Bの終点
優先順位および結合規則から演算子Bからですが、D〜G'が式なのでこちらからです。


D E F E' G H G'
b [ 0 ]  [ 3 ]


通し記号種別
Dint*[5]変数(b)
E演算子( [] )(その他、優先16、結合→)
Fconst int0定数
E'演算子Eの終点
G演算子( [] )(その他、優先16、結合→)
Hconst int3定数
G'演算子Gの終点
結合規則より演算子Eからです。
変数 b は int** へ暗黙変換されます。
0x0012FF30(bの絶対ID) + 4(bの要素型int*の大きさ) * 0(定数F) により、 0x0012FF30 です。


A     B C B'
vtemp1 [ 3 ]


通し記号種別
Aint*&0x0012FF44(a[0][0])一時変数(参照先:b[0](0x0012FF30))
B演算子( [] )(その他、優先16、結合→)
Cconst int3定数
B'演算子Bの終点
さらに、演算子Bを処理します。
0x0012FF44(一時変数A) + 4(一時変数Aの示す型intの大きさ) * 3(定数C) により、 0x0012FF50 となります。

これで一個になったので全体に戻ります。

A      B C        D      B'
printf ( "%d\n" , vtemp2 )


通し記号種別
Aint(*)(const char*,...)0x00401470関数(printf)
B演算子( () )(その他、優先16、結合→)
Cconst char[4]"%d\n"文字列定数
Dint&13一時変数(参照先:a[0][3](0x0012FF50))
B'演算子Bの終点
これで二個目の13が出力されます。


続いて、(5)14行です。

A B C
c = a


通し記号種別
Aint(*)[5]0x00000000(NULL)変数(c)
B演算子(=)(2項、算術、優先2、結合←)
Cint[3][5]変数(a)
暗黙変換により、変数Cは int(*)[5] になります。

A B C
c = vtemp1


通し記号種別
Aint(*)[5]0x00000000(NULL)変数(c)
B演算子(=)(2項、算術、優先2、結合←)
Cint(*)[5]0x0012FF44(a[0])一時変数
そのまま代入されます。

この時点までを実行した時の状況の一例
実体ID(絶対)実体の型保持値所属
0x0012FF44int[3][5]main(a,line 4)
0x0012FF44int[5]main(a[0],line 4)
0x0012FF44int10main(a[0][0],line 4)
0x0012FF48int11main(a[0][1],line 4)
0x0012FF4Cint12main(a[0][2],line 4)
0x0012FF50int13main(a[0][3],line 4)
0x0012FF54int14main(a[0][4],line 4)
0x0012FF58int[5]main(a[1],line 4)
0x0012FF58int20main(a[1][0],line 4)
0x0012FF5Cint21main(a[1][1],line 4)
0x0012FF60int22main(a[1][2],line 4)
0x0012FF64int23main(a[1][3],line 4)
0x0012FF68int24main(a[1][4],line 4)
0x0012FF6Cint[5]main(a[2],line 4)
0x0012FF6Cint30main(a[2][0],line 4)
0x0012FF70int31main(a[2][1],line 4)
0x0012FF74int32main(a[2][2],line 4)
0x0012FF78int33main(a[2][3],line 4)
0x0012FF7Cint34main(a[2][4],line 4)
0x0012FF30int*[5]main(b,line 5)
0x0012FF30int*0x0012FF44(main::a[0][0])main(b[0],line 5)
0x0012FF34int*NULLmain(b[1],line 5)
0x0012FF38int*NULLmain(b[2],line 5)
0x0012FF3Cint*NULLmain(b[3],line 5)
0x0012FF40int*NULLmain(b[4],line 5)
0x0012FF2Cint(*)[5]0x0012FF44(main::a[0])main(c,line 6)
0x0012FF28int(&)[5]0x0012FF44(main::a[0])main(e,line 8)
mainブロック 確保位置ID:0x0012FF28


続いて、(6)15行です。

A      B C        D E F E' G H G' B'
printf ( "%d\n" , c [ 0 ]  [ 3 ]  )


通し記号種別
Aint(*)(const char*,...)0x00401470関数(printf)
B演算子( () )(その他、優先16、結合→)
Cconst char[4]"%d\n"文字列定数
Dint(*)[5]0x0012FF44(a[0])変数(c)
E演算子( [] )(その他、優先16、結合→)
Fconst int0定数
E'演算子Eの終点
G演算子( [] )(その他、優先16、結合→)
Hconst int3定数
G'演算子Gの終点
B'演算子Bの終点
優先順位および結合規則から演算子Bからですが、D〜G'が式なのでこちらからです。


D E F E' G H G'
c [ 0 ]  [ 3 ]


通し記号種別
Dint(*)[5]0x0012FF44(a[0])変数(c)
E演算子( [] )(その他、優先16、結合→)
Fconst int0定数
E'演算子Eの終点
G演算子( [] )(その他、優先16、結合→)
Hconst int3定数
G'演算子Gの終点
結合規則より演算子Eからです。
0x0012FF44(cの値) + 20(cの示す型int[5]の大きさ) * 0(定数F) により、 0x0012FF44 です。


A     B C B'
vtemp1 [ 3 ]


通し記号種別
Aint(&)[5]一時変数(参照先:a[0](0x0012FF44))
B演算子( [] )(その他、優先16、結合→)
Cconst int3定数
B'演算子Bの終点
さらに、演算子Bを処理します。一時変数Aの int(&)[5] 型の暗黙変換は int[5] と同じです。
0x0012FF44(一時変数A) + 4(一時変数Aの要素型intの大きさ) * 3(定数C) により、 0x0012FF50 となります。

これで一個になったので全体に戻ります。

A      B C        D      B'
printf ( "%d\n" , vtemp2 )


通し記号種別
Aint(*)(const char*,...)0x00401470関数(printf)
B演算子( () )(その他、優先16、結合→)
Cconst char[4]"%d\n"文字列定数
Dint&13一時変数(参照先:a[0][3](0x0012FF50))
B'演算子Bの終点
これで結局 a[0][3] にたどりつき、三個目の13が出力されます。


次に注釈としてある(7)16行ですが、なぜアクセス違反になるのか追いかけてみましょう。

A      B C        D E F E' G H G' B'
printf ( "%d\n" , b [ 1 ]  [ 3 ]  )


通し記号種別
Aint(*)(const char*,...)0x00401470関数(printf)
B演算子( () )(その他、優先16、結合→)
Cconst char[4]"%d\n"文字列定数
Dint*[5]変数(b)
E演算子( [] )(その他、優先16、結合→)
Fconst int1定数
E'演算子Eの終点
G演算子( [] )(その他、優先16、結合→)
Hconst int3定数
G'演算子Gの終点
B'演算子Bの終点
優先順位および結合規則から演算子Bからですが、D〜G'が式なのでこちらからです。


D E F E' G H G'
b [ 1 ]  [ 3 ]


通し記号種別
Dint*[5]変数(b)
E演算子( [] )(その他、優先16、結合→)
Fconst int1定数
E'演算子Eの終点
G演算子( [] )(その他、優先16、結合→)
Hconst int3定数
G'演算子Gの終点
結合規則より演算子Eからです。
変数 b は int** へ暗黙変換されます。
0x0012FF30(bの絶対ID) + 4(bの要素型int*の大きさ) * 1(定数F) により、 0x0012FF34 です。


A     B C B'
vtemp1 [ 3 ]


通し記号種別
Aint*&0x00000000(NULL)一時変数(参照先:b[1](0x0012FF34))
B演算子( [] )(その他、優先16、結合→)
Cconst int3定数
B'演算子Bの終点
さらに、演算子Bを処理します。一時変数Aの int*& 型の暗黙変換は int* と同じです。
0x00000000(一時変数A) + 4(一時変数Aの示す型intの大きさ) * 3(定数C) により、 0x0000000C となります。

これで一個になったので全体に戻ります。

A      B C        D      B'
printf ( "%d\n" , vtemp2 )


通し記号種別
Aint(*)(const char*,...)0x00401470関数(printf)
B演算子( () )(その他、優先16、結合→)
Cconst char[4]"%d\n"文字列定数
Dint&????????一時変数(参照先:???(0x0000000C))
B'演算子Bの終点
この vtemp2 を評価できず、ここでアクセス違反になってしまいます。


さて、本編に戻って(8)17行です。

A      B C        D E F E' G H G' B'
printf ( "%d\n" , c [ 1 ]  [ 3 ]  )


通し記号種別
Aint(*)(const char*,...)0x00401470関数(printf)
B演算子( () )(その他、優先16、結合→)
Cconst char[4]"%d\n"文字列定数
Dint(*)[5]0x0012FF44(a[0])変数(c)
E演算子( [] )(その他、優先16、結合→)
Fconst int1定数
E'演算子Eの終点
G演算子( [] )(その他、優先16、結合→)
Hconst int3定数
G'演算子Gの終点
B'演算子Bの終点
優先順位および結合規則から演算子Bからですが、D〜G'が式なのでこちらからです。


D E F E' G H G'
c [ 1 ]  [ 3 ]


通し記号種別
Dint(*)[5]0x0012FF44(a[0])変数(c)
E演算子( [] )(その他、優先16、結合→)
Fconst int1定数
E'演算子Eの終点
G演算子( [] )(その他、優先16、結合→)
Hconst int3定数
G'演算子Gの終点
結合規則より演算子Eからです。
0x0012FF44(cの値) + 20(cの示す型int[5]の大きさ) * 1(定数F) により、 0x0012FF58 です。


A      B C B'
vtemp1 [ 3 ]


通し記号種別
Aint(&)[5]一時変数(参照先:a[1](0x0012FF58))
B演算子( [] )(その他、優先16、結合→)
Cconst int3定数
B'演算子Bの終点
さらに、演算子Bを処理します。一時変数Aの int(&)[5] 型の暗黙変換は int[5] と同じです。
0x0012FF58(一時変数A) + 4(一時変数Aの要素型intの大きさ) * 3(定数C) により、 0x0012FF64 となります。

これで一個になったので全体に戻ります。

A      B C        D      B'
printf ( "%d\n" , vtemp2 )


通し記号種別
Aint(*)(const char*,...)0x00401470関数(printf)
B演算子( () )(その他、優先16、結合→)
Cconst char[4]"%d\n"文字列定数
Dint&23一時変数(参照先:a[1][3](0x0012FF64))
B'演算子Bの終点
これで a[1][3] にたどりつき、三個目の23が出力されます。


今回は長かったですが、いかがでしたでしょうか。
今回は割と重要なところなので流れをしっかり把握しておくことをお勧めします。

申し訳ありませんが、他が忙しくなってきてしまったため、
次回更新はしばらく間が空いてしまいそうです。

次回は今回のゲームの設計についての予定です。

[前へ]   [目次へ]   [次へ]

プログラミング講座 総合目次

最終更新 2009/05/08