今回は、C++でよく混乱する「*・&・-> という記号の意味と使い分け」について解説していきます。
「* って掛け算じゃないの?」
「& がアドレスだったり参照だったり、どっち…?」
「-> と . はいつ使い分けるの?」
こんな疑問はありませんか?
実はこれ、経験者でも一瞬「あれ?」となる定番の混乱ポイントです。原因はハッキリしていて、同じ記号が「書く場所」によって別の意味になるから。逆に言えば、その「場所によるルール」さえ掴めば一気にスッキリします。
この記事を読み終えると、あなたは*・&・-> がそれぞれ何をしているか・場所による意味の違い・使い分け・初心者がハマりやすいミスをしっかり理解できると思いますので、ぜひ最後まで読んでいただけると嬉しいです。ポインタそのものが不安ならポインタとは?の記事を先に読むとスムーズです。
混乱の正体:記号は「場所」で意味が変わる
まず結論です。混乱の9割はこれで説明できます。
- 型を書く場所(宣言)にある記号 … その変数が「何者か」を表す
- 式の中(処理)にある記号 … 「何をするか」を表す
同じ * や & でも、宣言に出てきたのか、式に出てきたのかで役割がまるっきり違います。ここを分けて考えるのが最大のコツです。
& は「アドレスを取る」と「参照」の2役
& には大きく2つの顔があります。
int x = 10;
int& ref = x; // 【宣言】参照:xの別名を作る
int* p = &x; // 【式】 アドレスを取る:xの住所を得るポイントは & がどこにあるかです。
- 型のとなり(
int& ref)なら「参照」=別名を作る宣言 - 変数の前(
&x)なら「アドレス演算子」=住所を取り出す処理
参照の詳しい使い方は参照(&)とは?の記事で解説しています。
* も「ポインタ型」と「中身を取る」の2役
* もまったく同じ構図です。宣言か式かで意味が反転します。
int x = 10;
int* p = &x; // 【宣言】ポインタ型:「int の住所を入れる箱」
int y = *p; // 【式】 デリファレンス:住所の先にある中身(10)を取る
*p = 20; // 【式】 中身を書き換える → x が 20 になる- 型のとなり(
int* p)なら「ポインタ型」=住所を入れる箱の宣言 - 変数の前(
*p)なら「デリファレンス」=住所の先の中身を取る処理
ちなみに掛け算の *(a * b)は「2つの値の間」に置くので、これも場所で見分けられます。
-> は「(*p).member」の省略形
最後に -> です。これはポインタ越しにメンバ(中の変数や関数)にアクセスするための記号です。
struct Player {
int hp;
};
Player player;
Player* p = &player;
// この2行はまったく同じ意味
(*p).hp = 100; // デリファレンスしてから . でアクセス
p->hp = 100; // -> で一発(こっちが定番)つまり -> は (*p). の省略形です。(*p).hp は書くのが面倒でカッコも忘れやすいので、ポインタからメンバを触るときは -> を使う、と覚えれば十分です。クラスやメンバの話はクラスとは?の記事もどうぞ。
. と -> の使い分け
「. と -> どっち?」で迷ったら、手元にあるのが「実体」か「ポインタ」かで決まります。
Player a; // 実体
Player* p = &a; // ポインタ
a.hp = 100; // 実体 → ドット
p->hp = 100; // ポインタ → 矢印- 実体(そのもの)なら
.(ドット) - ポインタ(住所)なら
->(矢印)
「矢印は住所の先へ飛んでいくイメージ」と覚えると迷いません。
記号の意味まとめ表
ここまでを1枚にまとめます。迷ったらこの表に戻ってください。
| 記号 | 場所 | 意味 | 例 |
|---|---|---|---|
& | 型のとなり(宣言) | 参照(別名) | int& r = x; |
& | 変数の前(式) | アドレスを取る | p = &x; |
* | 型のとなり(宣言) | ポインタ型 | int* p; |
* | 変数の前(式) | 中身を取る | y = *p; |
-> | ポインタのあと | メンバにアクセス | p->hp |
. | 実体のあと | メンバにアクセス | a.hp |
【重要】私が実際に記号で混乱した体験談
プログラミングを始めた頃、int* p = &x; の * と & を見て「同じ行に両方いる…これは何算だ?」と完全に固まりました。掛け算と論理積の記号が混ざっているようにしか見えなかったんです。
転機は「宣言の記号と、式の記号を分けて読む」と教わったことでした。int* p は型の一部(pはポインタ)、&x は処理(xの住所を取る)。分けて読んだ瞬間、急に文章として読めるようになりました。
もう一つ、よくやったのが p.hp と書いてエラーにするミス。p はポインタなのにドットで書いてしまうやつです。「住所にドットは打てない、矢印で先へ飛ぶ」とイメージするようにしたら、間違えなくなりました。
記号の使い分けでよくある失敗例と対処法
①ポインタなのに . を使う
p.hp はエラー。ポインタからメンバを触るなら -> です。実体は .、ポインタは -> を徹底しましょう。
②デリファレンス忘れで住所を表示する
std::cout << p; と書くと、中身ではなく住所(変な数字)が出ます。中身が欲しいなら *p。「* で先の中身を取る」を忘れないでください。
③複数宣言で * の位置に惑わされる
int* a, b; と書くと、a だけポインタ、b はただの int になります(* は a にだけ掛かる)。混乱を避けるため、ポインタは1行に1つ宣言するのが安全です。
注意点
- 記号は「宣言」か「式」かで意味が変わる、とまず疑う
->は(*p).の省略形。ポインタ専用- ポインタは1行に1つ宣言するとミスが減る
まとめ
&:型のとなりなら参照、変数の前ならアドレス*:型のとなりならポインタ型、変数の前なら中身を取る->:(*p).の省略形。ポインタからメンバへ- 迷ったら「実体は
.、ポインタは->」
記号は「場所で意味が変わる」と分かれば、もう怖くありません。混乱したら、この記事のまとめ表に戻ってきてください。

