• 検索結果がありません。

多次元配列の定義

ドキュメント内 note.dvi (ページ 153-156)

第 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

ドキュメント内 note.dvi (ページ 153-156)