再びポインタと配列その3 (2次元配列とポインタ配列を関数の引数として渡す)
前回の「再びポインタと配列」でも、2次元配列とポインタについて触れましたが、今回はもう少し深く
掘り下げてみましょう。
今回は、配列を関数の引数としてに渡す場合について、掘り下げてみます。
いちおう、このシリーズは、BASICと比較しながら学ぶCということですが、さすがにこのあたりの話に
なると、BASICではついてこれません。”CによるCらしいプログラム”に少し入っていきます。
1 2次元配列を、1次元配列、ポインタ変数でうける
2次元配列を引数として、関数を呼ぶ場合、呼び出される側でも2次元配列を用意してうける、というのが
ごくオーソドックスなやり方です。しかし、もっとCらしいやりもあります。
どういうことかというと、Main関数内の2次元配列を、呼び出された関数側では、1次元配列や
ポインタ変数でうけるという例です。
もちろん、よびだされた関数側でも2次元配列として受け取れば、一番素直なわけですが、
1次元配列やポインタ変数として受け取ることも可能です。
とりあえず、コードをみてもらうのが、一番理解しやすいでしょう。
/* Coded by Tsuyoshi Kasai for LSIC */ /* 2次元配列の関数渡し */ #include <stdio.h> void dataprint(int *,int); void dataprint1(int *,int); int data[2][3] = {{0,10,20}, {30,40,50}}; main(){ dataprint(data[0],6); printf("\n"); dataprint1(data[0],6); } /* ポインタ変数でうける */ void dataprint(int* dt,int max) { int i; for (i=0;i<max;i++) printf("%3d",*dt++); } /* 1次元配列でうける */ void dataprint1(dt,max) int dt[],max; { int i; for (i=0;i<max;i++) printf("%3d",dt[i]); } |
2次元配列 data に対して、
dataprint 関数ではポインタ変数で受けています。
また、dataprint1 関数では1次元配列で受けています。
この関係を表にすると以下のようです。
2次元配列 | data[0][0] | data[0][1] | data[0][2] | data[1][0] | data[1][1] | data[1][2] |
ポインタ変数 | *dt | *(dt+1) | *(dt+2) | *(dt+3) | *(dt+4) | *(dt+5) |
1次元配列 | dt[0] | dt[1] | dt[2] | dt[3] | dt[4] | dt[5] |
実行画面です
C:\C>nijigen 0 10 20 30 40 50 0 10 20 30 40 50 C:\C> |
2 ポインタ配列(文字列)を、ポインタ配列、またはポインタ変数のポインタ変数でうける。
ポインタ配列は文字列の配列をあつかうのに便利なので、よく使われますが、今回は
ポインタ配列を、ポインタ配列またはポインタ変数のポインタ変数でうける例です。
(ポインタ変数のポインタ変数は **data のような記述になります。)
/* Coded by Tsuyoshi Kasai for LSIC */ #include <stdio.h> void dataprint(); void dataprint1(); main(){ static char *data[4] = {"I","am","Tsuyoshi","Kasai"}; printf("%s %s %s %s\n",data[0],data[1],data[2],data[3]); dataprint(data,4); dataprint1(data,4); } void dataprint(char *dt[4], int max) { int i; for (i=0;i<max;i++) printf("%s ",dt[i]); printf("\n"); } void dataprint1(dt,max) char **dt; int max; { int i; for (i=0;i<max;i++) printf("%s ",*(dt+i)); printf("\n"); } |
実行画面
C:\C>nijigen2 I am Tsuyoshi Kasai I am Tsuyoshi Kasai I am Tsuyoshi Kasai C:\C> |
見事、ポインタ配列をポインタ変数のポインタ変数に渡しています。
[補足]
ということで、終了にしてもいいのですが、
もうひとつ、ポインタ配列と、ポインタ変数のポインタ変数がすっきり理解できないという人もいるでしょうから、
上のプログラムを更にひとひねり加えて、それぞれ変数のポインタ(アドレス)も表示させてみました。
ついでに、ポインタ変数で、ポインタ配列をうけてみました。
/* Coded by Tsuyoshi Kasai for LSIC */ #include <stdio.h> void dataprint(); void dataprint1(); void dataprint2(); main(){ static char *data[4] = {"I","am","Tsuyoshi","Kasai"}; printf("data:%d\n",data); printf("%s %s %s %s\n\n",data[0],data[1],data[2],data[3]); dataprint(data,4); dataprint1(data,4); dataprint2(data); } /*ポインタ配列*/ void dataprint(char *dt[4], int max) { int i; printf("dt:%d dt[0]:%d dt[1]:%d dt[2]:%d dt[3]:%d\n",dt,dt[0],dt[1],dt[2],dt[3]); for (i=0;i<max;i++) printf("%s ",dt[i]); printf("\n\n"); } /*ポインタ変数のポインタ変数 */ void dataprint1(dt,max) char **dt; int max; { int i; printf("dt:%d *dt:%d *(dt+1):%d *(dt+2):%d *(dt+3):%d\n",dt,*dt,*(dt+1),*(dt+2),*(dt+3)); for (i=0;i<max;i++) printf("%s ",*(dt+i)); printf("\n\n"); } /* 参考おまけ(ポインタ変数) */ void dataprint2(dt) char *dt; { int i; printf("\ndt:%d\n",dt); for (i=0; i<30; i++) printf ("%c",*dt++); } |
実行画面
C:\C>nijigen3 data:102 I am Tsuyoshi Kasai dt:102 dt[0]:110 dt[1]:112 dt[2]:116 dt[3]:126 I am Tsuyoshi Kasai dt:102 *dt:110 *(dt+1):112 *(dt+2):116 *(dt+3):126 I am Tsuyoshi Kasai dt:102 n p t ~ I am Tsuyoshi Kasai C:\C> |
<−ポインタ配列 <−ポインタ変数のポインタ変数 <−ポインタ変数 |
一番下の結果を見てください。みての通り、ポインタ配列をポインタ変数で受けるという作業は、
うまくいってません。
I am の前に余計なものがついています。これはいったいなんでしょう?
2次元配列は、ポインタ変数(や、1次元配列)でうまくうけとることができましたが、ポインタ配列は
ポインタ変数ではうまく受け取れなかったようです。
こういう例を見せられると、頭が「2次元配列も、ポインタ配列も同じようにデータを扱えるはずなのに、???」
となってしまいますが、うっかりすると、やってしまいがちなミスプログラムのひとつです。
どうも、思ったような結果がでない、と2日も3日もいらいらして、バグを探すと、たいていこんなミスプログラム
をしでかしているものです。特にBASICプログラムから移行してくると、気が付きにくいC独特の部分です。
Cの解説書には、ここがCの柔軟でいいところとか、書いてありますが。
これは、言葉で書くと、ポインタ変数の場合は、dt は「ポインタ」であるのに対し、
ポインタ配列、およびポインタ変数のポインタ変数ではdt
は「ポインタのポインタ」だからです。
すなわち、ポインタの階層が1段階と2段階、という風に異なるのです。
と言っても、わかりにくいので、ちょっと表にまとめてみました。(コンパイラがLSICの場合です)
アドレス | メモリーの値 | ポインタ配列 |
102 | 110 | dt[0](=*dt[0]のポインタ=110) dtはdt[0]の先頭アドレス(ポインタ)=102を示す |
104 | 112 | dt[1](=*dy[1]のポインタ=112) |
106 | 116 | dt[2](=*dt[2]のポインタ=116) |
108 | 126 | dt[3](=*dt[3]のポインタ=126) |
110 | I | *dt[0] |
111 | \0 | \0は文字列の終わりの印 |
112 | a | *dt[1] |
113 | m | *(dt[1]+1) |
114 | \0 | |
116 | T | *dt[2] |
117 | s | *(dt[2]+1) |
118 | u | *(dt[2]+2) |
略 | ||
126 | K | *dt[3] |
アドレス | メモリーの値 | ポインタ変数のポインタ変数 |
102 | dt(=*dtのポインタ=102) | |
102 | 110 | *dt(=**dtのポインタ=110) |
104 | 112 | *(dt+1)(=**(dt+1)のポインタ=112) |
106 | 116 | *(dt+2)(=**(dt+2)のポインタ=116) |
108 | 126 | *(dt+3)(=**(dt+3)のポインタ=126) |
110 | I | **dt |
111 | \0 | \0は文字列の終わりの印 |
112 | a | **(dt+1) |
113 | m | *(*(dt+1)+1) |
114 | \0 | |
116 | T | **(dt+2) |
117 | s | *(*(dt+2)+1) |
118 | u | *(*(dt+2)+2) |
略 | ||
126 | K | **(dt+3) |
アドレス | メモリーの値 | ポインタ変数 |
102 | dt(=*dtのポインタ) | |
102 | 110 | *dt (アスキーコードで ”n”) |
104 | 112 | *(dt+2) (アスキーコードで”p”) |
106 | 116 | (アスキーコードで”t”) |
108 | 126 | |
110 | I | *(dt+8) |
111 | \0 | \0は文字列の終わりの印 |
112 | a | |
113 | m | |
114 | \0 | |
116 | T | |
117 | s | |
118 | u | |
略 | ||
126 | K |
なんなく、納得していただけたでしょうか