「C言語は難しい!」なぜなら「ポインタがあるから」といわれる程、ポインタはC言語を修得するための一つの山であるのは間違いなさそうです。しかしポインタを通して学ぶ関連知識は、Javaなどのオブジェクト指向言語の修得にも通じるものですから得るものも大きいです。相手にとって不足無し!頑張っていきましょう!!
コンピュータはプログラム内の変数をメモリー上に確保し、その場所を特定するためのアドレスを記憶しています。プログラムでそのアドレスを取り扱いたいときに使用するのがポインタです。つまり、ポインタとはアドレスを取り扱う変数のことです。
ポインタの構文は次の通りです。
データ型 *ポインタ変数名;
(例)int型変数のポインタを宣言するとき。
int *p;
変数に”&”をつけるとそのアドレスが取得できます。
&変数名;
・サンプルソース(sample0701.c)
#include <stdio.h>
int main() {
int x = 10;
int *p;
p = &x; /* アドレスの代入。ちなみに宣言と同時にアドレスを代入することもできます。int *p = &x; */
printf("「&x」のアドレス = %p\n", &x);
printf("「p」が示すアドレス = %p\n", p);
return 0;
}
・実行結果
C:\dev\c>sample0701 [Enter] 「&x」のアドレス = 0012FF88 ← 実行環境によって表示されるアドレス値は変わります。 「p」が示すアドレス = 0012FF88
ポインタに”*”をつけるとポインタが示すアドレス内の値が取得できます。
*ポインタ変数名;
・サンプルソース(sample0702.c)
#include <stdio.h>
int main() {
int x = 10;
int *p = &x;
/* 変数xのアドレスを代入したのでxの値が表示されるはず */
printf("「p」が示すアドレス内の値 = %d\n", *p);
return 0;
}
・実行結果
C:\dev\c>sample0702 [Enter] 「p」が示すアドレス内の値 = 10 ← xの値が表示された。
ポインタの宣言時にアドレスを代入する場合、先の例では「int *p = &x; 」となって「*p」に「&x」を代入したように見えますが、意味的には宣言時以外でそうしているように「p = &x」となります。ですから「*p」で取得する値はアドレスではなく、アドレス内の値となります。このことから、宣言時の代入を次のように書く人もいます。
int* p = &x;
これで宣言時も「p」に「&x」を代入しているように見えますが、この場合は次のような複数のポインタ変数を宣言するとき注意が必要です。
int* p1, p2;
上記はポインタ変数p1,p2を宣言しているのではなく、ポインタ変数p1とint型変数p2を宣言していると解釈されます。
ん~「ポインタ変数の宣言」と「アドレス内の値の取り出し」を別の記号であればわかり易いとおもうのですが・・・ どうでしょうか・・・。
ポインタ変数を宣言しただけでは、そのポインタ変数が示すアドレスは不定です。ですからポインタ変数を初期化(安全な領域のアドレスを代入)しないで使用すると不特定のアドレスにアクセスすることになり、システム破壊につながる深刻なバグになります。(コンパイラが注意してくれると思いますが。)
#include <stdio.h> int main() { int x = 10; int *p; *p = x; /* pを初期化(p = &x;)していません! 重大なバグです!! */ printf("「p」が示すアドレス内の値 = %d\n", *p); return 0; }
配列の先頭アドレスをポインタ変数に代入する事によりポインタ演算を使って配列を取り扱う事が出来ます。
・サンプルソース(sample0703.c)
#include <stdio.h> int main() { int *p, i, data[5] = {10,20,30,40,50}; p = data; /* 1. 配列の先頭アドレスをポインタ変数に代入。 */ for(i=0; i<5; i++) { printf("%d\n", *(p + i)); /* 2. ポインタを要素数分進めて値を取り出す。 */ } return 0; }
・実行結果
C:\dev\c>sample0703 [Enter]
10
20
30
40
50
上記サンプルを元に配列とポインタの関係を整理します。
配列変数data[]において
&data[0]はdataと同じである。
配列変数data[]、ポインタ変数p、int型変数iにおいて「p = data」のとき
*(p + i)はdata[i]と同じである。
関数もメモリ上に登録されており、そのアドレスを示すポインタ(関数ポインタ)を使用すれば関数を呼び出す事が可能です。関数ポインタの構文は次の通りです。
戻り値の型 (*関数ポインタ名)(引数リスト);
また宣言と同時に関数のアドレスを代入(初期化)する事も出来ます。
戻り値の型 (*関数ポインタ名)(引数リスト) = 関数のアドレス;
※関数のアドレスは関数から「()」(関数呼出し演算子)を省いた関数名で取得できます。
関数「func()」において「func」は関数funcのアドレスを表します。
関数ポインタの使用例は次のようになります。
・サンプルソース(sample0704.c)
#include <stdio.h> int add(int a, int b) { return (a + b); } int main() { int (*pfunc)(int, int) = add; printf("%d\n" , pfunc(1, 1)); return 0; }
・実行結果
C:\dev\c>sample0704 [Enter]
2
関数ポインタは配列にする事も可能です。宣言は次のようになります。
戻り値の型 (*関数ポインタ名[要素の数])(引数リスト);
宣言と同時に初期化するとき。
戻り値の型 (*関数ポインタ名[要素の数])(引数リスト) = { 関数のアドレス, 関数のアドレス, ・・・ };
・サンプルソース(sample0705.c)
#include <stdio.h> int add(int a, int b) { return (a + b); } int sub(int a, int b) { return (a - b); } int main() { int i; int (*pfunc[2])(int, int) = {add, sub}; for(i=0; i<2; i++) { printf("%d\n" , pfunc[i](1, 1)); } return 0; }
・実行結果
C:\dev\c>sample0705 [Enter]
2
0