kasei_sanのブログ

かせいさんのIT系のおぼえがきです。胡乱の方はnoteとtwitterへ

Rubyの代入では参照値が代入先に格納される

先に結論

  • rubyの代入はオブジェクトの参照値(ポインタのようなもの)が代入先に格納される
  • もし、格納元が参照値の場合、同一の参照値が代入先に格納される(参照の参照にはならない)
  • イメージとしては、C++のポインタの値の代入に近い

文字列"aaa"を変数aに代入する場合

a = "aaa"

上記の場合、以下の処理が実行される

  1. Stringオブジェクト"aaa" が生成される
  2. 変数aStringオブジェクト"aaa" の参照値が格納される

その変数aを別の変数bに代入するばあい

a = "aaa"
b = a

上記の場合、

  1. 変数 a に格納されている Stringオブジェクト"aaa" の参照値が、変数 b に 格納される

同じオブジェクトが参照されていることが object_id を比較すると分かる

a = "aaa"
b = a
a.object_id == b.object_id # true

変数b に破壊的メソッドsub!を実行した場合

a = "aaa"
b = a
b.sub!(/a/, 'b')

この場合、変数 ab も同じオブジェクトを参照しているため、変数 a の表示も変わる

a = "aaa"
b = a
b.sub!(/a/, 'b')

p b # "bbb"
p a # "bbb"

変数b に別の文字列 "bbb" を代入する場合

a = "aaa"
b = a
b = "bbb"

このばあい、変数b には新たに生成されたStringオブジェクト"bbb" の参照値が格納され、変数aとは参照先が別になる

変数bと変数aの参照先が異なることが object_id を比較すると分かる

a = "aaa"
b = a
b = "bbb"

a.object_id != b.object_id # true

ぎゃくに変数a に別の文字列 "bbb" を代入する場合

a = "aaa"
b = a
a = "bbb"

このばあい、変数a にはStringオブジェクト"bbb" の参照値が格納される

しかし、変数bはStringオブジェクト"aaa"の参照値が格納されたままのため、文字列 "aaa" が表示される

a = "aaa"
b = a
a = "bbb"

p b # "aaa"
a.object_id != b.object_id # true

上記のことから rubyの変数には参照ではなく、参照値が格納されていることが分かる

なんで?

もし、a="aaa"C++などで言う「参照」であるならば

b=a; b="bbb" としたときに Stringオブジェクト"aaa" の内容が "bbb" に変わるはず

C++での参照

#include <iostream>

int main(void){
  int x = 100;
  int& xr = x;
  xr = 200;
  // xの参照である xr の値を変更するとxの値も変更される
  std::cout << "x=" << x << "\n";  // 200
  return 0;
}

どちらかというと、C++でいうポインタの値の代入のイメージに近い

しかし、rubyではポインタは無いためJavaに倣って「参照値の代入」という言葉を選んだ

C++でのポインタの値の代入

#include <iostream>

int main(void){
  int a = 100;
  int* ap = &a;                     //apにaのポインタの値を格納
  std::cout << "ap=" << ap << "\n"; //aのポインタの値(0x7fff4fe752b8)

  int* bp = ap;                     //aのポインタの値をbpに格納(rubyで言う a=b)
  std::cout << "bp=" << bp << "\n"; //ap == bp

  int b = 999;
  bp = &b;                          //bpにbのポインタの値を格納(rubyでいう b=999)
  std::cout << "bp=" << bp << "\n"; // ap != bp(999)

  std::cout << "a=" << a << "\n"; // 当然 a の値は変更されない(100)
  return 0;
}