C++のポインタ入門:アドレス(&)と間接参照(*)の基本

C++において「オブジェクト」とは、int 型の変数 x のように、「値を表現するための記憶域(メモリ領域)」を指します。プログラムが実行されるとき、これらのオブジェクト(変数)はコンピュータのメモリ上のどこかに配置されます。

この記事では、その配置場所である「アドレス」を直接扱うための強力な機能、「ポインタ」について解説します。

目次

オブジェクトとアドレス(& 演算子)

すべてのオブジェクト(変数)は、メモリ上に固有の「番地(アドレス)」を持っています。C++では、アドレス演算子(& を変数の前に付けることで、その変数が格納されているメモリ上のアドレス値を取得できます。

アドレスは通常、OSや環境によって管理される16進数の大きな数値として表現されます。

#include <iostream>

int main() {
    int userLevel = 50;
    double itemPrice = 1980.5;

    // & 演算子を使って、各変数のメモリアドレスを表示する
    std::cout << "--- 変数のメモリアドレス ---\n";
    std::cout << "userLevel のアドレス: " << &userLevel << "\n";
    std::cout << "itemPrice のアドレス: " << &itemPrice << "\n";

    return 0;
}

実行結果(一例):

--- 変数のメモリアドレス ---
userLevel のアドレス: 0x7ffeea1b95c8
itemPrice のアドレス: 0x7ffeea1b95c0

このアドレス値は、プログラムを実行するたびに異なる場合があります。

ポインタとは?(*による宣言)

ポインタ (Pointer) とは、intdouble のような通常のデータ値ではなく、メモリアドレスを値として格納するための特殊な変数です。

ポインタを宣言するには、型名の後ろに * (アスタリスク)を付けます。

  • int* : int 型オブジェクトのアドレスを格納できるポインタ
  • double*: double 型オブジェクトのアドレスを格納できるポインタ
int main() {
    int currentScore = 500;
    
    // 1. ポインタ変数の宣言
    // 'scorePtr' は「int型へのポインタ」
    int* scorePtr;
    
    // 2. アドレスの代入
    // currentScore のアドレスを scorePtr に代入する
    scorePtr = &currentScore;

    std::cout << "currentScore の値: " << currentScore << "\n";
    std::cout << "currentScore のアドレス: " << &currentScore << "\n";
    std::cout << "scorePtr の値 (アドレス): " << scorePtr << "\n";
    
    return 0;
}

scorePtr の値が &currentScorecurrentScore のアドレス)と一致していることがわかります。この状態を「scorePtrcurrentScore指す (point to)」と表現します。

間接演算子(*)によるアクセス

ポインタの真価は、それが指す先にあるオブジェクトにアクセスできる点にあります。

間接演算子(Indirection Operator)またはデリファレンス演算子(Dereference Operator)と呼ばれる * をポインタ変数の前に付けると、そのポインタが指すアドレスに格納されているオブジェクトそのものにアクセスできます。

*scorePtr という式は、currentScore 変数の**別名(エイリアス)**と考えることができます。

#include <iostream>

int main() {
    int currentScore = 500;
    int* scorePtr = &currentScore;

    std::cout << "--- 初期状態 ---\n";
    std::cout << "元の変数の値: " << currentScore << "\n"; // 500
    // *scorePtr は「scorePtr が指す先の値」
    std::cout << "ポインタ経由の値: " << *scorePtr << "\n"; // 500

    // 1. ポインタ経由で値を変更する
    std::cout << "\n--- ポインタ経由で 100 を加算 ---\n";
    *scorePtr = *scorePtr + 100;
    
    std::cout << "ポインタ経由の値: " << *scorePtr << "\n"; // 600
    // *scorePtr を変更すると、元の currentScore も変更される
    std::cout << "元の変数の値: " << currentScore << "\n"; // 600

    return 0;
}

このように、*scorePtr = ... という操作は、currentScore = ... という操作と等価になります。

ポインタの動的な切り替え

ポインタ変数の値(指し示すアドレス)は、実行時に変更できます。これは、特定の状況に応じて操作対象を動的に切り替えるコードを実現する上で役立ちます。

& を使う「参照」は、一度初期化すると対象を変更できませんが、ポインタは変更可能です。)

以下の例では、ゲームの難易度設定に応じて、ダメージを適用する対象の体力プールをポインタで切り替えています。

#include <iostream>

int main() {
    int easyModeHealth = 200;
    int hardModeHealth = 100;
    
    // int型へのポインタを宣言
    int* activeHealthPool; 

    // (C++11以降) ポインタが何も指していないことを明示するために
    // 0 や NULL の代わりに `nullptr` を使うことが推奨されます。
    activeHealthPool = nullptr; 

    bool isHardMode = true; // ユーザーが難易度を選択したと仮定

    // 状況に応じて、ポインタが指す対象を切り替える
    if (isHardMode) {
        activeHealthPool = &hardModeHealth;
        std::cout << "難易度ハードが選択されました。\n";
    } else {
        activeHealthPool = &easyModeHealth;
        std::cout << "難易度イージーが選択されました。\n";
    }

    // これ以降のコードは、難易度を if 文で再チェックする必要がない
    std::cout << "現在の体力: " << *activeHealthPool << "\n";
    
    // ポインタ経由で、現在アクティブな方の体力を減らす
    int damage = 30;
    *activeHealthPool = *activeHealthPool - damage;

    std::cout << damage << " のダメージを受けました。\n";
    std::cout << "残りの体力: " << *activeHealthPool << "\n";

    // 元の変数が変更されていることを確認
    std::cout << "--- 最終結果 ---\n";
    std::cout << "イージーの体力: " << easyModeHealth << "\n"; // 200
    std::cout << "ハードの体力: " << hardModeHealth << "\n";   // 70 (こちらが変更された)

    return 0;
}

ポインタと現代のC++(注意点)

この記事で紹介した int* のようなポインタは「生のポインタ(Raw Pointer)」と呼ばれます。生のポインタはC++の強力な機能ですが、同時に多くの危険性もはらんでいます。

nullptr (C++11)

初期化されていないポインタ(「野良ポインタ」)は、どこを指しているか不定であり、そのまま使うとプログラムがクラッシュします。C++11以降では、ポインタが何も指していないことを示すために nullptr キーワードを使用します。ポインタ変数を宣言する際は、すぐに有効なアドレスを代入しない限り nullptr で初期化する習慣をつけることが推奨されます。

メモリ管理とスマートポインタ

生のポインタの最大の危険性は、new で動的に確保したメモリの解放を delete で忘れる「メモリリーク」です。

現代のC++プログラミングでは、メモリリークを防ぐため、生のポインタをメモリ管理に直接使用することは避けられます。代わりに、<memory> ヘッダにある**スマートポインタ(std::unique_ptrstd::shared_ptr)**を使用します。これらは、オブジェクトが不要になった時点で自動的にメモリを解放してくれる、より安全な仕組みです。

まとめ

  • **&(アドレス演算子)**は、変数が格納されているメモリアドレスを取得します。
  • **Type*(ポインタ宣言)**は、メモリアドレスを値として保持する変数を宣言します。
  • ***(間接演算子)**は、ポインタが指すアドレスにあるオブジェクトそのものにアクセスします。
  • ポインタは、指す対象を実行時に切り替えることができ、動的なコードを実現します。
  • 生のポインタは危険を伴うため、現代のC++では nullptr での初期化を徹底し、メモリ管理にはスマートポインタを使用することが強く推奨されます。
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次