公開日: 2010年08月30日(Mon)
かねてより何度も挑戦しては理解できなくて挫折していたポインタ。それがついに!理解できた(ような気がする)ので、メモとして残す。
まず、普通に値をコピーしてみる。ある変数から、別の変数に値をコピーするには、次のようにする。この手順では、コピー元とコピー先は別の実体となる。
#include <stdio.h>
int main(){
int a;
int b;
// a のコピー b の練習。
a = 1;
b = a;
printf("a: %d; b: %d;\n",a,b);
b = 5;
printf("a: %d; b: %d;\n",a,b);
return 1;
}
これを hellopointer.c というファイル名で保存し、コンパイルして実行してみる。
ちなみに、コンパイルするのは下記のコマンド。(ただし、gccコマンドがインストールされている必要がある)
# gcc -o hellopointer hellopointer.c
こうすると、 hellopointer という実行ファイルができているはずなので、それを実行。
# ./hellopointer
すると下記のように出力されるはずだ。
a: 1; b: 1;
a: 1; b: 5;
int型の変数 a と b を宣言し、a には1を、bにはaを代入した。
この時点で printf() した結果は、 a も b も 1 となる。
その後、b に 5 を代入しなおして、もう一度 printf() すると、a は 1 のままで、b は 5 に変更されている。
これは、参照になっていない、つまり、a と b は物理的に別物だということ。
次に参照渡しをやってみる。c を、a のポインタにする。
#include <stdio.h>
int main(){
int a;
int *c;
// a のポインタ *c の練習。
a = 1;
c = &a; //&で参照になる。PHPと似てる。もとい、PHPが似せている。
printf("a: %d; *c: %d;\n",a,*c);
*c = 5;
printf("a: %d; *c: %d;\n",a,*c);
return 1;
}
これをコンパイルして実行すると、下記の出力が得られる。
a: 1; *c: 1;
a: 5; *c: 5;
2行目の a の値が、先ほどの b の例と結果が変わった。
まず、実装の異なる部分は下記。
c を宣言するところが、先ほどの b の例と異なる。頭に * を付けて宣言している。cに値を代入するところも異なるようだ。a ではなく、&a を代入している。*c というように、*記号を付けている。
これで2行目の a の値が変わったということは、*c = 5; の結果、a に 5 が代入しなおされたということで、つまり、*c は a のポインタとして機能していることになる。
ちょっとこの実験だけだと、何が起きたのかわかりにくいかも知れない。もう少し解剖した実験をしてみると意味わかるかも。
a = 1; の直後に、printf("&a: %d;\n",&a); してみると、次のような出力が得られる。
&a: -1081655872;
値は実行するたびに異なる。これは多分なんだけど、メモリ上のアドレスのようなものを意味した値なんじゃないかと思う。頭に & を付けると、メモリ上のアドレスを返すという規則。この後 &a が c に代入されるわけだが、c を出力しても、当然ながら同じ値が得られる。
つまり、c には、アドレスの情報が代入されたということのようだ。そして、c の値が示すアドレスに実際にある値を参照したいときに付けるのが、*記号というわけ。
& と * の規則を理解したら、多重ポインタも簡単に理解できる。
ポインタ c のポインタ d を作る実験をやってみる。
#include <stdio.h>
int main(){
int a;
int *c;
int **d;
// a のポインタ *c のポインタ **d の練習。
a = 1;
c = &a;
d = &c;
printf("a: %d; *c: %d; **d: %d;\n",a,*c,**d);
*c = 3;
printf("a: %d; *c: %d; **d: %d;\n",a,*c,**d);
**d = 5;
printf("a: %d; *c: %d; **d: %d;\n",a,*c,**d);
return 1;
}
これをコンパイルして実行すると、出力は下記。
a: 1; *c: 1; **d: 1;
a: 3; *c: 3; **d: 3;
a: 5; *c: 5; **d: 5;
*c も **d も同じ実体 a を参照しているので、3つとも常に同じ値が出力されている。
この、**d を解剖していくと、理屈はさっきと同じ。
d には c のアドレスが格納されているので、*d は c の値である "aのアドレス" ということになる。*d は a のアドレスなので、 **d は、a の値、というカラクリ。
後は、3重でも4重でも、同じ理屈でアドレスを辿って、元の値を参照することができるわけだ。
過去に何度も挑戦してみて、何度も挫折してきたポインタだけど、わかってしまえば難しいことはなさそうだ。
名前領域の管理とか、型のキャストとか、まだわかんない部分は多いけど、もうちょい勉強したら何か作れそうかな?
公開日: 2010年08月30日(Mon)