第 6 章 C 言語入門
6.16 配列とポインタ(その3・多次元配列)
6.16.1 多次元配列の定義
はじめに多次元配列の定義を行う.
int str[2][5] ;
配列を構成する演算子 []の結合規則は「左から右」であるので, str[i]は,int型の5個の要素を持つ 配列であり, strは2個の要素を持つ「 int型の5個の要素を持つ配列」の配列である. 各種の教科書に は, 以下のような図が書かれていることが多い.
str[0][0] str[0][1] str[0][2] str[0][3] str[0][4]
str[1][0] str[1][1] str[1][2] str[1][3] str[1][4]
より正確にメモリ上でstrが表す2次元配列の様子を見ると,次の図のようになる.
str[0] str[1]
str[0][0] str[0][1] str[0][2] str[0][3] str[0][4] str[1][0] str[1][1] str[1][2] str[1][3] str[1][4]
多次元配列を初期化する方法は, char daytab[2][13] = {
{0,31,28,31,30,31,30,31,31,30,31,30,31}, {0,31,29,31,30,31,30,31,31,30,31,30,31}
} ;
とすれば良い.
Id: C7-2.tex,v 1.2 2001-03-21 19:38:00+09 naito Exp
Example 6.16.1 次の例は,N×N,N = 10の単位行列を作成している.
int i, j ;
int unit_mat[10][10] ; for(i=0;i<10;i++) {
for(j=0;j<10;j++) unit_mat[i][j] = 0 ; unit_mat[i][i] = 1 ;
}
Remark 6.16.1 多次元配列は,文法的には配列を識別子の型とする配列である. 配列の宣言では,「配列 の限界を指定する定数式がないときには,配列は不完全な型を持つ。」とあり,配列の要素の型は完全でな ければならない. これは, 多次元配列においては,最初の次元のみが省略できることを意味している. すな わち,明示的な初期化により配列を完全にすることが可能なので,
int y[][2] = { {1,2}, {2,3}, {3,4} } ;
により,この配列はint型の2つの要素を持つ配列の3つの要素を持つ配列として定義される. また,この 初期化は
int y[3][2] = { 1,2,2,3,3,4 } ; と等価である.
Remark 6.16.2 また, 次のような配列の初期化子による定義も可能である.
int y[][2] = { {1,2}, {2}, {3} } ;
この定義では,y[1],y[2]は初期化子には1つの要素しか持たないが,y[0]が2つの要素を持つため,y[1], y[2]は2つの要素を持つ配列として定義され,yは不完全な型を持つ配列となる.
しかし,これを
int y[][] = { {1,2}, {2}, {3} } ;
とは定義できない. この理由は,次のSection 6.16.2の多重配列をポインタで書換えることと関連している.
Remark 6.16.1, Remark 6.16.2の詳細については, [2, A8.6, A8.7]を参照.
6.16.2 多重配列とポインタ
1次元配列では,配列とポインタは,ほぼ同じものを示していた. すなわち, int a[3] ;
として与えられたオブジェクトに対して,a[0], *aまたはa[i],*(a+i)は,それぞれ,同じメモリ領域へ のアクセスを表し,a,&a[0]またはa+i,&a[i]も,それぞれ,同じアドレスを指し示していた. ここでは多 重配列においては,配列とポインタは(このような意味で)同じものと見なせるかどうかを考えてみよう.
はじめに,二重配列 int a[2][5] ;
を考えてみる. この時[2, A8.6.2]によれば,a[0][0],**a,またはa[i][j],*(*(a+i)+j),*(a[i]+j)は, それぞれ,同じ領域へのアクセスを示し,a,&a[0][0],または*(a+i)+j,a[i]+j,&a[i][j]も,それぞれ 同じアドレスを示す. このことを
Id: C7-2.tex,v 1.2 2001-03-21 19:38:00+09 naito Exp
a[0] a[1]
a[0][0] a[0][1] a[0][2] a[0][3] a[0][4] a[1][0] a[1][1] a[1][2] a[1][3] a[1][4]
を用いて考えてみよう.
これを理解するには,次の2つのポインタ演算の差を理解する必要がある.
1. aに対してa+1が何を表すか?
2. a[0]に対してa[0]+1が何を表すか?
これに対する解答のヒントとして, sizeof(a)
sizeof(a[0]) sizeof(a[0][0])
の式の値を見てみるのがよい. これをint型が4バイトの処理系で調べると,順に40,20,4という答えが 得られる.
上の図を見ると,aは「int型の5個の要素を持つ配列」という型の配列であるので,a+iはaから「int 型の5個の要素を持つ配列」のバイト数(20バイト)のi倍だけ先を表す. つまり,*(a+i)はa[i]と 等価である.
さらに,a[0]はint型の要素を持つ配列であるので,a[0]+j はa[0]からint型のバイト数(4バイ ト)のj倍だけ先を表す. つまり, *(a[0]+j)は a[0][j]と等価である.
したがって, a[i][j] は *(a[i]+j) と等価であり, *(*(a+i)+j) と等価であることがわかる. 当然,
*(a+i)+jはa[i][j]のアドレスを示すポインタとなる.
このことから,
int y[][] = {{1,2}, {2}, {3}} ; int x[][] = {{1,2}, {2,3}, {3,4}} ;
という定義では,配列y[i]に対するインクリメントy[i]+1のインクリメントのバイト数が計算できない ことになり,このような定義が認められないことがわかる.
同様に,3次元以上の配列も int a[2][5][10] ;
と定義できる. 2次元配列と同様に, 3次元以上の場合も以下の参照はすべて同じものとなる.
• a[i][j][k], *(a[i][j]+k),*(*(a[i]+j)+k),*(*(*(a+i)+j)+k).
• &a[i][j][k],a[i][j]+k,*(a[i]+j)+k, *(*(a+i)+j)+k.
したがって,適切な初期化子をおくことにより, int a[][5][10] = {{
{0,1,2,3,4,5,6,7,8,9}, {1,2,3,4,5,6,7,8,9,0}, {2,3,4,5,6,7,8,9,0,1}, {3,4,5,6,7,8,9,0,1,2}, {4,5,6,7,8,9,0,1,2,3}}, {
{1,2,3,4,5,6,7,8,9,0}, {2,3,4,5,6,7,8,9,0,1}, {3,4,5,6,7,8,9,0,1,2}, {4,5,6,7,8,9,0,1,2,3}, {5,6,7,8,9,0,1,2,3,4}}} ; という定義が可能である.
Id: C7-2.tex,v 1.2 2001-03-21 19:38:00+09 naito Exp