参照の値渡し - 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
に変わっただけです.
JavaScript,Pythonでも書いてみました.
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);
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