C++言語講座 第2章 2回 ポインタ
ポインタ
ポインタ
前回も見たように、参照はいくつか使いにくい点があります。参照の代わりに使えて、もっと細かい操作が出来るようにしたのがポインタ (pointer)です。ポインタとは指すものという意味です。
参照は型名の後に & を付けて宣言しましたが、ポインタは * を付けて、以下のように宣言します。
int* p;
これで、int型のオブジェクトを指す事の出来るポインタが出来ました。参照は宣言するときに必ず初期化しなければなりませんでしたが、ポインタの場合は初期化しなくても構いません。
ポインタが何かを指すようにしましょう。
int x;
p = &x;
int型のオブジェクトxを使って、pに &x を代入しているようです。この & は参照演算子(reference operator) と呼ばれ、 &x とは「オブジェクト x を指すポインタ」という意味となります。これを p に代入しているわけです。これにより、p は x を指している状態になりました。
次に、p が指しているオブジェクト(つまり、x)の内容を表示してみましょう。
cout << *p << endl;
* は & の反対で、*p とすることで「ポインタ p が指しているオブジェクト」という意味になります。この * は逆参照演算子(dereference operator) と呼ばれます。つまり、この場合 *p と書くのは x と書くのと同じ意味ということです。
次に、p が指しているオブジェクト(つまり、x)に他の値を代入してみましょう。
*p = 10;
*p は x という意味でした。つまり、この式は x に10を代入しているわけです。
では、参照では出来なかった事をしてみましょう。今 p は x を指していますが、他のオブジェクトを指すように変えてみましょう。
int y;
p = &y;
最初に p が x を指すようにしたときと同じように、& で y をポインタに変換し、p に代入すれば良いのです。
ポインタを使ったプログラムの例です。
[List 1]
1 #include <iostream>
2 using namespace std;
3
4 int main()
5 {
6 int x = 10, y = 20;
7 int* p;
8
9 p = &x;
10 *p = 30;
11
12 p = &y;
13
14 cout << x << endl << y << endl << *p << endl;
15
16 return 0;
17 }
ポインタの操作で大事なことは、以下の二つの処理の違いを理解することです。
p = &x; // …(1)
*p = y; // …(2)
(1)と(2)の違いが分かりますか? (1)は「p を、x を指すポインタにしている」。 (2)は「*p (つまり、x)に y を代入している」。
引数としてポインタを使う
参照と同じように、ポインタも関数への引数として使うときに、真価を発揮します。
[List 2]
1 #include <iostream>
2 using namespace std;
3
4 void func(int* p)
5 {
6 *p = 20;
7 }
8
9 int main()
10 {
11 int x = 10;
12
13 func(&x);
14
15 cout << x << endl;
16
17 return 0;
18 }
参照のときと違って、& と * の使い方に注意して下さい。
NULLポインタ
ポインタは参照と違って、宣言するときに初期化しなければならないわけではありません。では、
int* p;
等と宣言されたポインタ p は、最初は何を指しているのでしょう。実は、何を指しているのかは分からないのです。まるっきりそのときの運次第。とは言っても、大抵はちゃんとしたオブジェクトは指していないので、*p 等とした途端、プログラムはクラッシュしてしまいます。
ポインタは参照と違って、常に何かを指していなければならないわけではありません。そこで、ポインタが何もさしていない、と明確に言うために、 NULLポインタ(ヌルポインタまたはナルポインタ、NULLは零の意)というものがあります。
p = NULL;
と書くと、ポインタ p にNULLポインタを代入したことになります。
NULLポインタを代入したからといって、何が起こるわけでもありません。ただ、以下のような使い方をします。
[List 3]
1 #include <iostream>
2 using namespace std;
3
4 void func(int* p)
5 {
6 if (p == NULL)
7 cout << "エラー:pはNULLです!" << endl;
8 else
9 cout << *p << endl;
10 }
11
12 int main()
13 {
14 int x = 10;
15
16 func(&x);
17
18 func(NULL);
19
20 return 0;
21 }
この関数funcは、引数のポインタが指しているオブジェクトを表示します。 NULLが渡された場合は、エラーを表示します。ポインタを引数に取る関数は原則として、ポインタがNULLではないか確認するべきだとされています。
NULLが代入されているポインタも、*p などとして使おうとした場合、プログラムがクラッシュします。
ポインタを使うときの注意点
参照と同じく、以下のようなプログラムはうまく動きません。
[List 4]
1 #include <iostream>
2 using namespace std;
3
4 int* func()
5 {
6 int x = 10;
7
8 return &x;
9 }
10
11 int main()
12 {
13 int* y;
14
15 y = func();
16
17 cout << *y << endl;
18
19 return 0;
20 }
この時も、yが指すべきオブジェクト(func関数のx)はすぐに破棄されてしまうので、cout でyを表示しようとした途端、プログラムはクラッシュしてしまいます。
メンバ関数
通常のオブジェクトや参照のメンバ関数を実行する場合は
オブジェクト名.メンバ関数名
と . を挟みましたが、ポインタの指しているオブジェクトのメンバ関数を実行するときは、-> を挟み、
ポインタ名->メンバ関数名
とします。
例:
1 string str;
2 string* p = &str;
3
4 p->length();
この
p->length();
は
(*p).length();
の略だと思って下さい。
ポインタの型名
ポインタも、やはりオブジェクトであることには変わりありませんから、型名があります。例えば
int* p;
と宣言したポインタの型名も、参照と同じくint*型と呼ばれます。頭の片隅に留めておいて下さい。
上で見たように、ポインタは使い方を間違えるとすぐにプログラムをクラッシュさせてしまいます。世の中にあるバグのほとんどはポインタの使い方を間違えたために起こるとも言われています。それだけ、ポインタは難しく使いにくいものですが、マスターすれば、ポインタは非常に強い味方となってくれます。
