ポインタと配列と文字列と


・ポインタと配列と文字列

 今まで別々に説明してきたこの3つは実は密接な関係があります。 混乱するかもしれませんが分かってしまえばより一層理解を深めることができるでしょう。

・ポインタと配列

 配列変数のポインタというものを考えてみましょう。 例えば

int a[5]={5,6,7,8,9};
これはa[0],a[1],a[2],a[3],a[4]という5個の要素を持っており、それぞれのポインタをp0,p1,p2,p3,p4とすると
(ソースと区別するために全角で書きます)
p0=&a[0]、*p0=a[0]=5
p1=&a[1]、*p1=a[1]=6
p2=&a[2]、*p2=a[2]=7
p3=&a[3]、*p3=a[3]=8
p4=&a[4]、*p4=a[4]=9
が成り立っています。 こんな風に要素数だけポインタが必要そうですが実は配列のある特徴によりひとつだけで済ます方法があります。 その特徴とは配列は各要素がメモリ上に並んで確保されている、つまり各要素のアドレスが隣あっているということです。 (本当はちょっとだけ違うのですが)
&a[1]=&a[0]+1
&a[2]=&a[0]+2
&a[3]=&a[0]+3
&a[4]=&a[0]+4
ということは配列の先頭の要素a[0]のポインタをpとすると
p0=&a[0]        =p  、*p0=*(p  )=a[0]=5
p1=&a[1]=&a[0]+1=p+1、*p1=*(p+1)=a[1]=6
p2=&a[2]=&a[0]+2=p+2、*p2=*(p+2)=a[2]=7
p3=&a[3]=&a[0]+3=p+3、*p3=*(p+3)=a[3]=8
p4=&a[4]=&a[0]+4=p+4、*p4=*(p+4)=a[4]=9
このように全てpのみで表せます。 そしてさらにCでは&a[0]をa、*(p+x)をp[x]と書くことができます。 上を書き換えると
p0=a  =p  、*p0=p[0]=a[0]=5
p1=a+1=p+1、*p1=p[1]=a[1]=6
p2=a+2=p+2、*p2=p[2]=a[2]=7
p3=a+3=p+3、*p3=p[3]=a[3]=8
p4=a+4=p+4、*p4=p[4]=a[4]=9
こうなります。 ポインタと配列を似たように表記できることが分かったと思います。 結局、ポインタに配列を指させるには
int a[5]={5,6,7,8,9};
int *p;

p=a; //p=&a[0];と同じ
とやります。 これで今までの式が成り立つ状態になります。

 関数に配列変数を渡す時、受け取る側は

void func(int *a) //又はvoid func(int a[])  ←未習時の章(○×ゲーム)ではこうしていました。
こうで呼び出す側は
func(b);
のように配列名を添え字なしで書きます。 さきほどでてきたように添え字なしの配列名は先頭の要素のアドレスを表すので実は&b[0]を渡しているのです。 *aとポインタで受け取った時も配列で受け取った時同様a[2]などのように扱えます。 表記を変えて配列っぽくしてるだけで本当は*(a+2)を扱ってるんですけどね。

 p++(つまりp=p+1)とやるとpが次のアドレスのメモリ上を指すようになります。 最初の例だと

p0=a  =p−1、*p0=p[−1]=a[0]=5
p1=a+1=p  、*p1=p[0]=a[1]=6
p2=a+2=p+1、*p2=p[1]=a[2]=7
p3=a+3=p+2、*p3=p[2]=a[3]=8
p4=a+4=p+3、*p4=p[3]=a[4]=9
このようにpが先頭ではなく二番目の要素を指すようになります。 この技は配列でない変数のポインタでも使えます(使いませんが)。
int a=5;
int *p;

p=&a; //ここでは*p=5、p[0]も5、p[1]は謎
p++; //p=&a+1になる、*pは謎←さっきのp[1]、今はp[0]
pはaが確保されているメモリの次の場所を指すようになりました。 でもこの場所はこのプログラムが確保した場所ではないので中身の予想はつきません。 配列の時に範囲外の添え字を指定した時も同じです。 関係ないところはくれぐれも書き換えたりしないようにしてください。

・と文字列

 文字列の章で文字列はchar型の配列と考えることができると書きました。 そしてついさっき配列とポインタの関係を学びました。 そこで今度は文字列のポインタを考えてみましょう。

char str[]="Mansaku";
char *p=str; //先頭のアドレスを代入
ちゃんとp[0]='M',p[1]='a',…,p[7]='\0'となります。 配列の時と同じですね。 さらにこんなこともできます。
char *p="Mansaku";
1ステップ省いてしまいましたが最初のと同じようになります。 普通はこのようなことはできません。
int a=5;
int *p=&a;
をまとめて
int *p=5;
とやったらエラーが出ます。 5はただの値でありアドレスを持ちません。 ポインタはあくまで指す物でありaという格納場所が決まって初めてそこを指すことができるのです。 要するにポインタはアドレスを代入するためのものであり値を直接は代入できないということです。
char *p="Mansaku";
で、この場合ですが右辺がMansakuではなく"Mansaku"とダブルクォーテーションに囲まれているのが上の場合との違いです。 つまり"Mansaku"がMansakuというchar配列のメモリを用意した時の先頭のアドレスを表していてそれをpに代入しているのです。 だから文字列はダブルクォーテーションで囲んでいたんですね。

 というわけで文字列を扱うにはchar型の配列かポインタを使うのですが両者にはちょっと違いがあります。

char str1[]="Mansaku";
char *str2="TaTo";
 str2はポインタなのでstr2=str1;のように指す対象を変更することでstr2の中身を変えたかのように、str2にstr1を代入したかのように扱うことが可能ですが、逆にstr1=str2とすることはできません。 str1の意味するところは&str1[0]でありこれは変数ではありません。 アドレスは定数です。 だから代入できません。 str1に他の文字列を代入したい時は要素をひとつずつ代入していくかstring.hの関数を使いましょう。 ただし、上のように配列の要素数を省略して宣言した場合は初期値分のメモリしか確保されてないのでそれより長い文字列は入れられません。 後から長い文字列を入れたい場合は十分大きい要素数を指定して宣言しておいてください。 このへんの注意事項は文字列の章の時の注意と照らし合わせて読みましょう。

 文字列型などという型は存在しません。 あくまでの文字の配列やポインタです。 表記にだまされることなくそれぞれの性質を思い出して使ってください。

・課題

さて何が表示されるでしょう。

int a[3][3]={{1,2,3},{4,5,6},{7,8,9}};

printf("%d\n",*(*(a+1)+2));









・解答

*(*(a+1)+2)=*(*(&a[0]+1)+2)=*(*(&a[1])+2)=*(a[1]+2)=*(&a[1][0]+2)=*(&a[1][2])=a[1][2]
よって6が表示されます。