こんにちは。ざわかける!のざわ(@zw_kakeru)です。
C系の言語で使われるポインタについて解説します。
この手の記事は山のようにあるので、私は実際の記述方法について現場レベルで整理してみます。
はじめに
今回のサンプルコードです。
これについて詳しく解説する気はないので、とりあえずスルーして大丈夫です。
最後にもう一度出てきます。
int main(void) {
int a = 10;
int* pa;
pa = &a;
printf("%d", *pa); // 10
*pa = 200;
printf("%d", *pa); // 200
printf("%d", a); // 200
}
宣言と参照
それではさっそくこの記事の肝をお話しします。
ポインタを操作する際に用いる記号「*」アスタリスクですが、記述する場面によって意味合いが異なります。
(個人的にここが一番初心者が分からなくなるところです。)
この意味合いについて解説していきます。
具体的には「宣言時」と「参照時」です。
ポインタに限らず、変数というのはまず変数宣言をしてから使って(参照して)いくことかと思います。
この宣言を行う時と参照を行う時とで同じ記号で使い方が違います。
普通に記述する際にはポインタ変数を宣言してから参照しますが、今回は説明の都合上「参照時」の説明の後に「宣言時」の説明を行います。
(そっちの方が分かりやすいかと思われます。)
ポインタ変数の参照
ポインタ変数の参照時のお話です。
ここでいう参照時は、要するに値にアクセスしたり書き換えたり、普通に使う時のことです。
さて、参照時の「*」は、「ポインタの指すアドレスの先」を意味します。
よって
*pa = 200;
printf("%d", a) // 200
と書けば参照先(a)を200に書き換えることができますし、
pa = &a;
printf("%d", *pa) // aの値
と書けばpaでaのアドレス値を受けることができます。
&は「アドレス値」を取得できます。「&a」は「a」のアドレス値を意味しています。
ここでは左辺(pa)はアドレスを格納するポインタ変数で、右辺(&a)も実際のアドレスを返しています。
アドレスのやり取りで完結していて中身にアクセスしていないのでこの場合は「*」は必要にならないわけですね。
そして3行目のprint時には変数の中身、すなわち「ポインタの指すアドレスの先」を出力するために「*」が必要になります。
(「*」が無いとpaそのもの、すなわちaの番地情報が出力されてしまいます。)
ポインタ変数の宣言
続いて宣言時のお話です。
先程の参照時の「*」とは全く異なることを意識してください。
ポインタ変数は「私はポインタ変数ですよ」ということが分かるように型宣言の後に「*」をつけます。
int* pa;
int * pa;
int *pa;
上記の3種類はどれも同じ意味なので好きな書き方をしてください。
これでポインタ変数の宣言は完了です。
簡単ですね。ここでつまづく人はあまりいないんじゃないかと思います。
そして、ここでの「*」の役割は「私はポインタ変数ですよ」と主張しているだけでそれ以外に意味を持ちません。
つまり前述の「ポインタの指すアドレスの先」の意味を持たないということです。
よって例えば
int *pa = 300;
このように宣言時に直接アドレス先に代入するような書き方はできません。
(左辺がアドレス値を格納するポインタ変数、右辺が実際の値となっており整合性が取れていないため。)
まとめ
宣言時の「*」アスタリスクはただ宣言してるだけであまり意味はないので書き方だけ覚えればよく、参照時の意味についてしっかりと理解しておけばバッチリでしょう。
その際に「*」アスタリスクの使い方が宣言時とは違うよということを覚えておけば頭がこんがらがらなくて済むでしょう。
最後に、もう一度最初に載せたサンプルコードを載せておきます。
int main(void) {
int a = 10;
int* pa;
pa = &a;
printf("%d", *pa); // 10
*pa = 200;
printf("%d", *pa); // 200
printf("%d", a); // 200
}
今なら少し読めるようになったのではないでしょうか。
おわりに
ポインタの概念はとっつきにくく、何をやりたいのか理解はできるけど自分では書けない、という状況に陥りがちです。
結局この記事を書いてる本人(私)が一番理解できるって話ですね。
そしてだからこそ分かるのですが、この記事を読んだだけで理解できる人もあまりいないでしょう。
しかしながら、私の説明で腑に落ちる人も絶対に存在するんですよね。
自分が理解できていない原因を特定するためにも、色々な書かれ方をしているネットの記事や書籍を当たってみるのが良いのではないかなと思います。
私がポインタが理解できなかった一番の原因は宣言時と参照時で「*」アスタリスクの意味が異なるということに気づけなかった(誰も教えてくれなかった)ためです。
私と同じところでつまづいている人の役に立てたなら、この記事を書いた意味もあるってもんですね。
ポインタのポインタやmallocによるメモリ確保などを扱うことになっても、根本的な書き方は変わらないので今回の記事の内容を覚えておきましょう。
以上です。
余談
(その1)
よく見かける
int a;
scanf("%d", &a);
でお馴染みのscanfでもこの&を使った書き方は出てきますね。
ユーザーから受け取った入力を「&a」、すなわち変数aのアドレスに格納することによってaでの操作を可能にしてるわけです。
(その2)
int a = 10;
int* pa;
*pa = 200;
このようにいきなり*paに値を格納することはできません。
2行目の段階ではまだpaの初期値が定まっておらず、どこのアドレスを指しているか不確定であるためです。
どこのアドレスか分からないのにそこに200を代入せよ、ということはできませんよね。
コンパイルは通るかもしれませんが実行するとコアダンプ等で落ちると思います。