【C++】参照(&)とは?|ポインタとの違い・使い分けを初心者向けに解説

サムネイル画像 C++

今回は、C++で初心者が混乱しやすい「参照(&)」について解説していきます。

「参照って結局何?」
「ポインタと何が違うの?」
「関数に渡す時は、ポインタ渡しと参照渡しのどっちがいいの?」

こんな疑問はありませんか?

ポインタの記事を読んだ後に、次に気になりやすいのがこの参照です。見た目は少し似ていますが、考え方はかなり違います。

この記事を読み終えると、あなたは参照の意味・ポインタとの違い・使い分け・初心者がハマりやすいミスまでしっかり理解できるようになると思いますので、ぜひ最後まで読んでいただけると嬉しいです。

参照(&)とは?

まず結論からいうと、参照は「変数の別名」です。

ポインタは「変数のアドレスを持つ変数」でしたが、参照は少し違います。参照は、ある変数に対して別の名前を付けて使っているだけ、と考えるとかなり分かりやすいです。

たとえば、value という変数に ref という参照を作ると、ref を変更した時は元の value も変わります。

▼main.cpp


#include <iostream>

int main() {
    int value = 10;
    int& ref = value;

    ref = 30;

    std::cout << value << "\n";
    std::cout << ref << "\n";
    return 0;
}

実行結果

30
30

このように、ref は value のコピーではありません。valueそのものを別名で扱っているイメージです。

なぜ参照が必要なのか?

参照が便利なのは、関数に変数を渡す時です。通常の値渡しだと、関数の中で値を変えても元の変数には反映されません。

ですが、参照渡しなら、関数の中で変更した内容をそのまま呼び出し元へ反映できます。しかも、ポインタのように *-> を毎回書かなくていいので、かなり読みやすいです。

ポインタの基礎がまだ曖昧な方は、ポインタの記事を先に読んでおくと、この後の違いもかなり理解しやすいです。

参照の基本的な使い方

まずは、関数で参照を使う基本例を見てみましょう。

▼main.cpp


#include <iostream>

void AddTen(int& value) {
    value += 10;
}

int main() {
    int score = 50;

    AddTen(score);

    std::cout << score << "\n";
    return 0;
}

実行結果

60

これは score を参照渡ししているので、関数の中で加算した結果がそのまま反映されています。値渡しではこうはなりません。

ポインタと参照の違いとは?

ここが一番大事なポイントです。

  • ポインタ:変数のアドレスを持つ
  • 参照:変数の別名として使う
  • ポインタ:nullptr を持てる
  • 参照:基本的に「何も参照していない状態」にはしない
  • ポインタ:指す先を後から変えられる
  • 参照:一度結びついた相手を途中で別の変数へ変更しない

実際にコードで並べてみると、違いがかなり見えやすいです。

▼main.cpp


#include <iostream>

int main() {
    int hp = 100;

    int* ptr = &hp;
    int& ref = hp;

    *ptr -= 10;
    ref -= 20;

    std::cout << hp << "\n";
    return 0;
}

実行結果

70

どちらも元の hp を変更できていますが、ポインタは *ptr と書く必要があり、参照はそのまま ref を使えるのが大きな違いです。

ゲームの関数にPlayerクラスを渡す時はどっちがいい?

ゲーム制作だと、PlayerクラスやEnemyクラスを関数へ渡す場面がかなり多いです。こういう時、「必ず有効なオブジェクトがある前提なら参照」「存在しない可能性があるならポインタ」と考えるとかなり整理しやすいです。

クラスの基本がまだ不安な方は、クラスの記事もあわせて読むと理解しやすいです。

▼main.cpp


#include <iostream>

class Player {
public:
    int hp;
};

void DamageByReference(Player& player) {
    player.hp -= 20;
}

void DamageByPointer(Player* player) {
    if (player == nullptr) {
        return;
    }

    player->hp -= 10;
}

int main() {
    Player hero;
    hero.hp = 100;

    DamageByReference(hero);
    DamageByPointer(&hero);

    std::cout << hero.hp << "\n";
    return 0;
}

実行結果

70

この例だと、DamageByReference は「必ず player が存在する」前提の書き方です。一方で、DamageByPointernullptr チェックができるので、「対象が存在しないかもしれない」時に向いています。

【重要】私が実際に参照とポインタで困った体験談

私も自主制作ゲームで、プレイヤーや敵の更新処理を関数に分け始めた頃に、参照とポインタの使い分けでかなり迷いました。

最初は「とりあえず全部ポインタで渡しておけば柔軟だろう」と考えていたのですが、毎回 nullptr チェックが必要になったり、-> が増えてコードが読みづらくなったりして、逆に管理しづらくなりました。

そこで、必ず存在するオブジェクトは参照存在しない可能性があるものだけポインタ、という形に整理したところ、かなりコードが見やすくなりました。個人的には、初心者のうちはこの基準で分けるのがかなりおすすめです。

参照使用時のよくある失敗例と対処法

1. 値渡しと参照渡しを混同してしまう

関数の引数に & が付いていないと、基本的にはコピーです。

  • 失敗例:関数内で変更したのに元の値が変わらない
  • 対処法:元の変数を変更したいなら参照渡しかポインタ渡しを使う

2. 参照なのに「後から別の変数へ付け替えられる」と思ってしまう

参照は別名なので、ポインタのように指す先を入れ替える感覚では使いません。

  • 失敗例:参照を再代入で別の変数へ結び直せると思う
  • 対処法:付け替えたい可能性があるならポインタの方が向いている

3. 参照なら安全だと思って中身の確認を雑にする

参照そのものは扱いやすいですが、元のオブジェクトの状態が正しいとは限りません。

  • 失敗例:参照先のメンバ値が壊れているのに参照のせいだと思い込む
  • 対処法:ブレークポイントやログ出力で、実際の値を確認する

こういう時は、Visual Studioでのデバッグ方法もかなり役立ちます。ブレークポイントで一行ずつ確認すると、参照やポインタのどこで値が変わったのか見つけやすいです。

注意点

  • 参照は「別名」であり、ポインタとは考え方が違う
  • 必ず有効なオブジェクトがあるなら参照はかなり書きやすい
  • 存在しない可能性があるならポインタの方が向いている
  • 迷ったら「nullptr が必要かどうか」で考えると整理しやすい

まとめ

  • 参照は変数の別名として使う仕組み
  • ポインタはアドレスを持つ変数で、参照とは考え方が違う
  • 参照は *-> を使わずに扱えるので読みやすい
  • 必ず存在するオブジェクトには参照、存在しない可能性があるならポインタが基本
  • 初心者は「使い分けの基準」を先に持っておくとかなり迷いにくい

個人的には、「普段は参照でシンプルに書き、nullptrが必要な場面だけポインタを使う」という考え方がおすすめです。この基準を持つだけでも、C++の関数設計はかなり整理しやすくなります。

関連記事