/*01*/ #include <stdio.h>
/*02*/
/*03*/ main()
/*04*/ {
/*05*/ int x[5];
/*06*/ int *p;
/*07*/ int n;
/*08*/
/*09*/ p = x;
/*10*/
/*11*/ for(n = 0; n < 5; n++){
/*12*/ *p = n*n;
/*13*/ p++;
/*14*/ }
/*15*/
/*16*/ for(n = 0; n < 5; n++){
/*17*/ printf("x[%d] = %d\n", n, x[n]);
/*18*/ }
/*19*/
/*20*/ return 0;
/*21*/ }
「p = x;」とありますがこれは「p = &x[0]」と同じ意味です。つまり、配列の文字部分だけを書くとそれは配列の先頭のアドレスを表すのです。この1行で、ポインタ変数「p」には配列 x の先頭(x[0])アドレスが入ったことになります。
さて、配列はメモリ上に連続的に確保されるので、ポインタ変数に配列のアドレスを代入すれば「次のアドレス」「前のアドレス」といった感じに、ポインタを使って操作することができます。この例では、まずポインタ変数 p には、配列 x の先頭アドレス、すなわち x[0] のアドレスが入っています。ですから、 p を使うことで x[0] が操作できます。for 文の最初のループでは「*p = n*n」とすることで x[0]に n*n つまり「0」を代入をすることになります。
では、隣のアドレス、つまり x[1] を操作するにはどうすればいいのでしょうか?もちろん「p = &x[1]」というふうにしてもいいのですが・・・、もう分かりますね。「p++;」がそれです。ポインタ変数に「1足す(インクリメントする)」ということは、次の領域のアドレスを入れるという事になります。ここで注意してください。ポインタ変数には「362」のようなアドレスを指す数値が入っているわけですが、ポインタ変数に「1」足した場合この値が「362」から「363」になるのではありません。自動的に変数の型分だけ足されるのです。この例では int 型なので自動的に 4byte 分が足されで、「362」から「366」になるのです。また、「p += 2」などとして、2つ先のアドレスを入れることもできますし、「--」「-=」などを使って前のアドレスを入れることもできます。.
int x[4]; int *p; p = x; このようにした場合 p の初期値 = 362 | | p = 370 |------- p += 2 ------->| | | | p = 366 p = 370 p = 374 |-- p++ --->|-- p++ --->|-- p++ --->| | | | | ------------------------------------------------------- ...| 362 - 365 | 366 - 369 | 370 - 373 | 374 - 377 |... ------------------------------------------------------- |<- x[0] -->|<- x[1] -->|<- x[2] -->|<- x[3] -->| |<- 4byte ->|
ところで、「x」と書くのは「&x[0]」と同じことですから、「x」自体を「x++」のようにインクリメントしたりすることは出来ませんので注意してください。
次に進む前に補足です。
int *p[4];
/*01*/ #include <stdio.h>
/*02*/
/*03*/ main()
/*04*/ {
/*05*/ int x[3][4][5];
/*06*/ int *y[3][4];
/*07*/ int m, n, q;
/*08*/
/*09*/ for(m = 0; m < 3; m++){
/*10*/ for(n = 0; n < 4; n++){
/*11*/ y[m][n] = x[m][n];
/*12*/ }
/*13*/ }
/*14*/
/*15*/ for(m = 0; m < 3; m++){
/*16*/ for(n = 0; n < 4; n++){
/*17*/ for(q = 0; q < 5; q++){
/*18*/ *y[m][n] = 100*m + 10*n + q;
/*19*/ y[m][n]++;
/*20*/ }
/*21*/ }
/*22*/ }
/*23*/
/*24*/ for(m = 0; m < 3; m++){
/*25*/ for(n = 0; n < 4; n++){
/*26*/ for(q = 0; q < 5; q++){
/*27*/ printf("x[%d][%d][%d] = %d\n", m, n, q, x[m][n][q]);
/*28*/ }
/*29*/ }
/*30*/ }
/*31*/
/*32*/ return 0;
/*33*/ }
多次元配列の場合を考えてみましょう。基本的には多次元と言っても、前回書いたようにメモリ上では直線的に順番に並んでいるので、ポインタ変数を次々とインクリメントしていけばそのとおりの順に操作できます。ですが、それだと直感的に扱いにくいので、作った配列に対応するポインタ配列を作ります。例としてはちょっと複雑ですが3次元配列を使って説明しますけれども、それほど難しくはないと思います。なお、説明を分かりやすくするため、「次元」の考え方ではなく前回やった「階層」の考え方で説明を進めていきます。
まずはもっとも下の階層を操作するために、それ以上の階層に対応するポインタの配列を作ります。どういうことかというと、各最下層を操作するために各最下層の先頭アドレスが入ったポインタを用意すればいいのです。最下層についてはポインタをインクリメントしたりして操作するため、その分の階層はいらないわけです。
次に初期化ですが、「y[m][n] = x[m][n]」の部分は、例えば「y[2][2] = x[2][2];」は「y[2][2] = &x[2][2][0];」と同じ意味です。つまり、最下層を除いた部分を書くとそれは最下層の先頭アドレスを表します。x[2][2][] 階層の先頭アドレスを入れているわけですが、考え型としては1次元の時と同じなので理解できるでしょう。そしてここでは、添字用の数値変数と for 文を使って効率よくポインタ配列と対応させて初期化しています。
あとは普通のポインタといっしょです。ポインタの指す先を操作したければ「*」を使えばいいし、次のアドレスに移りたければポインタをインクリメントすればいいのです。
問題12-1 で、配列を操作する際にポインタを使うように作り直してください。
問題12-2 で、配列を操作する際にポインタを使うように作り直してください。
#include <stdio.h>
main()
{
int wa, data[20];
int i, n;
int *p;
printf("データ数 -> ");
scanf("%d", &n);
/* ポインタ変数を配列の先頭にセット(p = &data[0] と同じ) */
p = data;
for(i = 0; i < n; i++){
printf("data[%d] -> ", i);
scanf("%d", p);
p++;
}
wa = 0;
p = data;
for(i = 0; i < n; i++){
wa += *p;
p++;
}
printf("総和は %d です。\n", wa);
return 0;
}
#include <stdio.h>
main()
{
/* 次のような配列、変数を用意する
data[20] 最大20個のデータが入る配列
i, j ループカウント用の変数
n データ数を入れておく
tmp 二つのデータを入れ替える時の退避変数
*p 配列を操作するためのポインタ変数
*/
int data[20];
int i, j, n, tmp;
int *p;
printf("データ数 -> ");
scanf("%d", &n);
/* ポインタ変数を配列の先頭にセット。 */
p = data;
for(i = 0; i < n; i++){
printf("data[%d] -> ", i);
scanf("%d", p);
/* ポインタを一つインクリメントして、隣の配列を
参照させる */
p++;
}
/* 何セット目かをカウントするループ */
for(i = 0; i < n; i++){
p = data; /* セットごとにポインタを初期化 */
for(j = 0; j < n - i - 1; j++){
if(*p > *(p + 1)){
tmp = *p;
*p = *(p + 1);
*(p + 1) = tmp;
}
/* 隣同士を比較するのに、(*p > *(p + 1)) とせずに、
もうひとつポインタ変数を用意してもよい。
すなわち *p2 を用意して、それぞれ
p = &data[0]
p2 = &data[1]
と初期化しておくわけである */
p++;
}
}
p = data;
printf("並べ替えました。\n");
for(i = 0; i < n; i++){
printf("data[%d] -> %d\n", i, *p);
p++;
}
return 0;
}