読者です 読者をやめる 読者になる 読者になる

頭の整理

JavaScript, Ruby, RSpec, Node.js, Rails TDDなどに興味があるWeb系SEが学んだことを整理していきます

参照の値渡し - Ruby, JavaScript, Python

Ruby JavaScript Python

多くのプログラミング言語では値渡しが基本ですが,渡す値が参照だった場合に参照先の配列の中身を変更できたりするのでプログラミングを始めたばかりの人が混乱することがあります.

まず,参照の値渡しとはある変数が保持している「参照の値」を別の変数に渡すことです.「参照の値」とは,メモリ番地のことです.
※厳密には違う場合もありますが,今回書きたいこととは話題が違ってしまうのでもうメモリ番地だということにします.

今回は,同じスコープ内で配列を別の変数に渡す場合を例にします.メソッドの引数に値を渡すケースがよくあげられますが,同じスコープ内での受け渡しも同じ話です.

※この時点で「当たり前だろ」って思う人はこの記事を読まなくても大丈夫な人だと思います.

Rubyを使って説明します.

例えば,for文などを使って以下の様な配列を生成するコードを書くとき.

[[1, 2, 3],[4, 5, 6],[7, 8,9]]


下記のように記述したとします.

 ary = []
 tmp = []
 num = 0

 for i in 0...3 do
   for j in 0...3 do
     num = num + 1
     tmp[j] = num
   end
   ary[i] = tmp
 end

 p ary


ここで上記の生成したい配列が作れないことが分かる人も,もうこの記事を読まなくても大丈夫な人です.web上にはもっと有益な情報が沢山転がっています.

ary[i] = tmp

この部分でtmpという配列の参照を渡しているため,aryの要素はすべて同じ配列への参照が代入されています.
言い換えると,ary[i]にはtmp配列の参照の値が入っていることになります.これが参照の値渡しです.

tmp配列は最後のループで

[7, 8, 9]

という内容になるため,最終的にary配列は以下の内容になります.

[[7, 8, 9],[7, 8, 9],[7, 8, 9]]


これを避けるためにはtmp配列をコピーした別の配列を作ればいいので,Arrayクラスが持つdupメソッドで配列をコピーします.
目的の配列を生成するには以下のように変更します.

ary = []
tmp = []
num = 0

for i in 0...3 do
  for j in 0...3 do
    num = num + 1
    tmp[j] = num
  end
  ary[i] = tmp.dup
end

p ary

tmp配列を代入する部分が

  ary[i] = tmp.dup

に変わっただけです.

JavaScriptPythonでも書いてみました.

JavaScript

var seikai = []
  , zannen = []
  , tmp = []
  , num = 0
;

for (var i = 0; i < 3; i++) {
  for (var j = 0; j < 3; j++) {
    num = num + 1;
    tmp[j] = num;
  }
  zannen[i] = tmp;
  seikai[i] = tmp.slice();
}

console.log(zannen);
console.log(seikai);


Python

num = 0
tmp = [0] * 3
seikai = [0] * 3
zannen = [0] * 3

for i in range(3):
  for j in range(3):
    num = num + 1
    tmp[j] = num

  zannen[i] = tmp
  seikai[i] = tmp[:]

print zannen
print seikai

参考ページ

Rubyist Magazine - 値渡しと参照渡しの違いを理解する
http://magazine.rubyist.net/?0032-CallByValueAndCallByReference