パソコン活用研究シリコンバレー(C、C++、の活用研究)

再びポインタと配列その3 (2次元配列とポインタ配列を関数の引数として渡す)

前回の「再びポインタと配列」でも、2次元配列とポインタについて触れましたが、今回はもう少し深く
掘り下げてみましょう。
今回は、配列を関数の引数としてに渡す場合について、掘り下げてみます。
いちおう、このシリーズは、BASICと比較しながら学ぶ
ということですが、さすがにこのあたりの話に
なると、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  

なんなく、納得していただけたでしょうか

TopPage