配列とポインタ

  ポインタと配列とは非常に深い関係があります.例えば,
int x[4] = {100, 200, 300, 400};
int *y;   // int y[4]; では,y = x; は実行できない
char *c;		
のような宣言がなされていたとして,以下の説明を行います.このとき,配列変数 x に対しては,図 6.1 の左に示すようなイメージを浮かべると理解しやすいと思います.つまり,x がポインタ変数であり,この変数の値が,連続した領域にある 4 つの変数 x[0],x[1],x[2],及び,x[3] の先頭アドレスを指しているというイメージです.従って,ポインタ変数 x の値を同じポインタ変数である y に代入,つまり,
y = x; または y = &(x[0]);		
という文を実行してやれば,図 6.1 の右に示すように,変数 x と変数 y はほとんど同じものになります.例えば,y[2] は x[2] と同じ箇所を参照することになります.ただし,厳密には,全く同じではありません.例えば,「y++」などの演算は可能ですが,「x++」は不可能です.x と y を全く同じように扱いたい場合は,x に対する定義部分を以下のように修正してやる必要があります.
int *x = new int [4];
for (int i1 = 0; i1 < 4; i1++)
	x[i1] = 100 * (i1 + 1);
int *y;
char *c;		
  また,先頭アドレスではなく,例えば「 y = &x[2] 」とすることにより,y を x の後半 2 つのデータに対応する 2 個の要素を持った配列として使用することも可能です.つまり,y[0] と x[2],及び,y[1] と x[3] が同じものになります(右図参照).

  上で述べたように,変数 y はポインタですので,間接演算子を使用して配列の各要素を参照することも可能です.たとえば, *y という方法で,y[0] ( x[0] でもある.以下,同様)を参照できます.それでは,ポインタを使用して y[1] 以下を参照するにはどのようにすれば良いでしょうか.まず,
y++;
*y = ・・・;		
のように,y の値を 1 だけ増加させれば,ポインタ変数 y は y[1] のアドレスを指すようになります.y は int 型のポインタ変数ですので,単純に 1 だけは増加しません.図 6.1 のような場合であれば,int 型変数の大きさ( 4 バイト)だけ増加し,24 という値になり,y[1] の記憶場所を指すことになります.y[2] 以降を参照する場合も,同様に,ポインタ変数を 1 ずつ増加させていけば可能です.

  しかし,この方法では,ポインタ変数 y の値自身が変化してしまい,後に同じ配列を参照したいとき困ることになります.もちろん,他のポインタ変数,例えば z にポインタの値を代入した後に上の操作を行えば問題ありませんが,通常,y[1],y[2],・・・ を,
*(y+1) = ・・・;
a      = *(y+2);
 ・・・		
のように参照する方法の方がよく使用されます.

  ポインタ変数の値を実際 1 だけ増加させたいような場合は,char 型のポインタ変数を使用すれば可能です.例えば,
c = (char *)x;
c++;		
とすれば,変数 c の値は 1 だけ増加し,図 6.1 の場合,21 になります.

  2 次元以上の配列に対しても,1 次元の場合と同様に,ポインタを介して参照等が可能です.例えば,2 次元配列の場合,以下のような宣言がなされていたとします.
nt a[3][2];
int x[3][2] = {{100, 200}, {300, 400}, {500, 600}};
int **y, *z, *w[3];		
  このとき,配列とポインタの関係は,概念的に,図 6.2 の左側のような関係になります.まず,実際にデータが記憶される場所として,x[0][0] から x[2][1] の連続した 6 個の領域がとられます(この例では,それらが,20 番地からの領域であるとしています).添え字が最も右側から変化していることに注意して下さい(この点は,3 次元以上の配列の場合も同様です).次に,x[0] から x[2] のポインタを要素とする配列があり,その各要素は,配列の各行の値が保存されている場所の先頭を指しています.変数 x は,このポインタ配列の先頭を指していることになります.したがって,変数 x は,ポインタ変数 y(ポインタに対するポインタ)と同等のものになり,
y    = &(w[0]);
w[0] = x[0];
w[1] = x[1];
w[2] = x[2];		
の記述により,例えば,x[1][1],y[1][1],及び,w[1][1] は,すべて同じ場所を指すことになります.

  また,配列に対し連続的な領域が確保されていますので,図 6.2 の右に示すように,2次元の配列を 1 次元の配列として処理することも可能です.例えば,
z = (int *)x;   // z = &x[0][0]; でも良い
		
のような処理をすれば,z[3] と x[1][1] が同じ場所を指すことになります.一般に,x[i][j] と z[(i×k) + j] は,同じ場所を指すことになります.ただし,k は,配列変数 x の宣言における列の数です(上の例では 2 ).

  3 次元以上の配列の対しても,ポインタ配列が増えていく以外,2 次元配列と同様です.例として,

int x[4][3][2];

と宣言した場合の概念図を図 6.3 に示します.この場合,変数 x の最も右側の添え字から順に変化していく点に注意して下さい.また,ポインタの概念からすると,変数 x は,

int ***y;

と宣言されたポインタ変数 y と同等のものになります.