参照とメモリ

参照の本質を理解。メモリとアドレスの基本

概要

参照(&)の動作を理解するには、 メモリとアドレスの概念を知る必要があります。

この章では、変数がメモリのどこに保存されているか、 参照はそれをどのように利用するかを学びます。

メモリとアドレス

コンピュータのメモリは線形に並んだバイト単位のセルです。 各セルには固有のアドレス(メモリ番地)があります。

イメージ:

アドレス: 1000  1004  1008  1012  1016
           |    |    |    |    |
値:        [42]  [0]  [35]  [0]  [88]
           (int) ----  (int) ----  (int)

int型の変数は通常4バイトなので、 アドレス1000から1003までを占めます。

アドレス演算子(&)

& は参照渡しの記号であり、同時にアドレス演算子でもあります。 変数のメモリアドレスを取得します。

#include <iostream>

int main() {
    int x = 42;
    
    std::cout << "値: " << x << std::endl;
    std::cout << "アドレス: " << &x << std::endl;
    
    return 0;
}

値: 42
アドレス: 0x7ffc8b4ef4ac

0x7ffc8b4ef4acのような16進数がメモリアドレスです。 実行するたびに異なる値になります。

参照とは何か

参照は、変数のアドレスを保存し、 それを通じて元の変数にアクセスします。

#include <iostream>

int main() {
    int original = 100;
    int & ref = original;  // ref は original への参照
    
    std::cout << "original: " << original << std::endl;
    std::cout << "ref: " << ref << std::endl;
    std::cout << "&original: " << &original << std::endl;
    std::cout << "&ref: " << &ref << std::endl;
    
    ref = 200;  // ref を通じ て original を変更
    std::cout << "original: " << original << std::endl;
    
    return 0;
}

original: 100
ref: 100
&original: 0x7ffc8b4ef4ac
&ref: 0x7ffc8b4ef4ac
original: 200

reforiginal は同じアドレスを指しています。

参照の特性

1. 参照はエイリアス

int x = 10;
int & ref = x;  // ref は x のエイリアス(別名)

ref++;
std::cout << x << std::endl;  // 11(ref を通じて x が変更された)

2. 参照は初期化時に決定

int a = 10, b = 20;
int & ref = a;  // ref は a を参照

ref = b;  // これは ref に b のアドレスを割り当てるのではなく
          // a に b の値を代入する
std::cout << a << std::endl;  // 20
std::cout << &ref << std::endl;  // &a と同じ

3. 参照は null になれない

int & ref;  // エラー! 参照は初期化が必須
int & ref = nullptr;  // エラー! 参照は null になれない

参照渡しの本質

参照渡しは、実はアドレスを渡しているのですが、 ユーザーから見えない形で行われます。

#include <iostream>

void modify(int & x) {
    x = 999;
}

int main() {
    int value = 10;
    std::cout << "変更前: " << value << std::endl;
    
    modify(value);  // value のアドレスが渡される
    
    std::cout << "変更後: " << value << std::endl;
    return 0;
}

変更前: 10
変更後: 999

modify関数はvalueのアドレスを受け取り、 それを通じて元の値を変更します。

実践例:複数の値の交換

#include <iostream>

void swapInts(int & a, int & b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 10, y = 20;
    
    std::cout << "交換前: x=" << x << ", y=" << y << std::endl;
    
    swapInts(x, y);  // x と y のアドレスが渡される
    
    std::cout << "交換後: x=" << x << ", y=" << y << std::endl;
    return 0;
}

交換前: x=10, y=20
交換後: x=20, y=10

参照と const

例1: const参照(読取専用)

int x = 10;
const int & ref = x;

std::cout << ref << std::endl;  // OK: 読取可能
// ref = 20;  // エラー!: const 参照は変更不可

例2: const変数への参照

const int x = 10;

int & ref = x;  // エラー!: const を非const参照に割り当てできない
const int & ref = x;  // OK: const参照なら OK

参照の活用パターン

パターン 形式 用途
入力のみ const Type & 値を読むだけ、変更しない
入出力 Type & 値を変更する
出力のみ Type & 関数内で値を設定する
効率的な読取 const Type & 大きなデータをコピーしない

ポイント

  • 参照本質はエイリアス(別名)
  • &はアドレス演算子でもあり、参照宣言でもある
  • 参照は初期化時に決定され、その後参照先を変更できない
  • 参照は自動的にデリファレンスされる(ユーザーは意識しない)
  • const参照は効率的で安全な入力方法

よくある誤り

誤り1: 参照を変更しようとする

int a = 10, b = 20;
int & ref = a;
ref = b;  // b に参照を変更するのではなく、a に b の値を代入

誤り2: const参照への代入

int x = 10;
const int & ref = x;
ref = 20;  // エラー!: const 参照は変更不可

誤り3: スコープ外への参照を返す

int & getRef() {
    int local = 10;
    return local;  // 危険!: local はここで破棄される
}

やってみよう

練習1: 3つの整数を受け取り、ソートする関数(参照渡し)。

練習2: 配列の各要素を参照渡しで増加させる関数。

練習3: Min/Max を参照で返す関数。

チャレンジ: テンプレート関数を使ったデータ型別の交換関数。

まとめ

  • 参照は変数のエイリアス、メモリ効率的でパワフル
  • 参照本質を理解があれば、パワーあるC++コードを書ける
  • const参照は入力パラメータの標準パターン
  • 参照は初期化時に決定され、その後変更できない(ポインタとの違い)