ポインタの配列

「2次配列」と「ポインタの配列」の違い

例えば、3つクラス1組、2組、3組があり、それぞれ25人の学生がいて、それぞれの点数を記憶して、 データを管理したい場合を考えてみます。

このような場合、 2次配列を利用する次の宣言を、過去に紹介しました。
int ten[3][25];
これは、『 int型が[25]個並ぶ領域 』の配列を3個用意する配列で、 次のようなイメージの記憶域が用意されます。

□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□

このイメージは、□の1つがint型要素で、右側にある程、そのアドレスが大きくなるメモリ空間です

n_kumi に、2組の2を記憶し、 n_gaku に25番目学生の意味で25を記憶し、 これを利用して、その人に100点の数を記憶したい場合、次のように記憶すると決めて使います。
ten[n_kumi-1][n_gaku-1]=100;

つまり、1組で1番目の人は、 ten[0][0] に記憶することになり、
つまり、2組で1番目の人は、 ten[1][0] に記憶(上記の所です)することになり、
つまり、3組で1番目の人は、 ten[2][0] に記憶することになります。

ポインタの配列

さてこのような場合、 ポインタの配列で管理する方法があり、その宣言は次のようになります。
int *ten[3];
これは、『 int型がデータを指し示すポインタ 』を3個用意する配列で、次のメモリイメージを作ります。

管理したいのは、クラス3つで、クラスの情報がある(学生の点数が並ぶ)領域の先頭位置で、管理するための変数です。 よって各要素の型は、(int *)型です。
これに、学生の点数が並ぶ領域の先頭位置を記憶させ、それから使うことになります。
学生の点数が並ぶ領域の先頭位置を記憶予定の要素なので、すぐに使える変数ではありません。
つまり、別途に各クラス分で、学生の点数を記憶する配列を用意する必要があります。
例えば、別途にmemoryの記憶域を作ります。3クラス×25人=75 より、75の要素で用意すればサイズ的に足ります。
int memory[75];

□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□

そして、この記憶域を、ポインタ配列の各要素へ、例えば次のように設定してから使うことになります。
ten[0]=memory; /* 1組の情報記憶位置の設定 */
ten[1]=memory+25; /* 2組の情報記憶位置の設定 */
ten[2]=memory+50; /* 3組の情報記憶位置の設定 */

このようポインタ要素を設定した後で、やっと使えることになります。
例えば、ten[0]は1組の情報の先頭位置(int*)型を記憶しているので、それに配列演算子を使うことで、各学生のint型データを アクセスできます。
n_kumi に、2組の2を記憶し、 n_gaku に25番目学生の意味で25を記憶し、 これを利用して、その人に100点の数を記憶したい場合、2次配列の場合と似ている次の操作が可能になります。
ten[n_kumi-1][n_gaku-1]=100;

つまり、1組で1番目の人は、 ten[0][0] に記憶することになり、
つまり、2組で1番目の人は、 ten[1][0] に記憶(上記の所です)することになり、
つまり、3組で1番目の人は、 ten[2][0] に記憶することになります。

なお、配列名 ten の表現は (int *)型 を指し示すポインタなので、 その型は、 (int **)型 という表現になります。

さてポインタ配列が、2次配列の使い勝手と大きく違うとことは、 各ポインタ要素が指し示すデータ群の個数が自由ということです。
 (対応するようにプログラムを書く必要がありますが・・)
例えば、1組の人数が25人、2組が15人、3組が35人とします。

これを、2次配列で行なうと、最も多い、35の配列で配列をつくらなければなりません。
int ten[3][35];/* 必要ないのに、3×35=105個の記憶域が作られます */

ですが、ポインタの配列の方は、3つのクラスを管理することに変りはないので、 tenの配列宣言に変更の必要はありません。 使う前の初期設定を次のように変更すれば使えることになります。
ten[0]=memory; /* 1組の情報記憶位置の設定 */
ten[1]=memory+25; /* 2組の情報記憶位置の設定 */
ten[2]=memory+25+15; /* 3組の情報記憶位置の設定(残りの35が使える) */
これにより、int型75個のmemoryで3クラスを管理できることができます。

□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□

ポインタの配列の初期化

すでに用意してある記憶域のアドレス表現を使って、次のような初期化が可能です。

int memory[3][25];
int *ten[3] = { memory[0], memory[1], memory[2] }; /* 2次配列の要素(int *)で初期化*/
int memory[75];
int *ten[3] = { memory, memory+25, memory+25+15 }; /* 1次配列の要素位置で初期化*/

ポインタ(char *)型の配列の初期化 (文字列で可能)

文字列定数は、その文字(char)並びの先頭位置を表現する(char *)型なので、 (char *)型を要素とする配列の初期化で使うことができます。

char *a[] = {	/* [ ] の中に 3が省略されている */
	"ab",
	"x",
	"1234"
};

この場合、使われている文字列定数が、メモリ空間のどこかに配置されます。
便宜上、次の番地に配置されたとします。

100番地 101番地 102番地 103番地 104番地 105番地 106番地 107番地 108番地 109番地
'a' 'b' '\0' 'x' '\0' '1' '2' '3' '4' '\0'

この場合、aの配列のメモリーイメージは次のようになります。

a[0] a[1] a[2]
100番地 103番地 105番地

なおこのイメージで、a[1][0] の文字から始まる文字列をputsで表示する場合は、
puts( a[1] )または、puts( & a[1][0] )で、できます。

char型2次配列の文字列による初期化←と、イメージを比較ください。

ポインタ要素の2次配列

次のようにポインタ要素を配列にして、それを配列にしたイメージです。

char n[] = "□";
char o[] = "○";
char x[] = "××";

char *a[][3] = {	/* [ ] の中に 2が省略されている */
	{ n,x,n },
	{ n,o,o },
};