【C++】* ・ & ・ -> の意味と使い分けとは?|ポインタの記号を初心者向けに整理

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). の省略形。ポインタからメンバへ
  • 迷ったら「実体は .、ポインタは ->」

*は、型のとなりならポインタ型、変数の前なら、中身を取る記号です。->は、(*p).の省略形で、ポインタからメンバへアクセスします。迷ったら、「実体は.、ポインタは->」と考えてください。

関連記事