PHPを勉強し始めたのですが、RubyやPythonといった言語と変数・配列の扱いが異なって戸惑っています。
一番わからないのが参照代入です。
http://www.programming-magic.com/20080307090613/
このサイトで挙動が解説されてますが、イマイチ納得できません。
PHPの変数の参照代入に関して詳しく解説しているサイトを教えてください。
あー、これ分かりにくいね。
$array[0] = 1;
$array[1] = 2;
$array[2] = 3;
ここは問題ないね。
ポイントは次
$ref = &$array[1];
この式を抜けた結果、$refはリファレンス(ポインタ/参照)に、そして$array[1]もリファレンス(ポインタ/参照)に変化すると考えると分かりやすいかも。
つまりこの段階でこんな感じになっている
$array[0] = 1;
$array[1] → とある変数空間 ← $ref
$array[2] = 3;
そして次
$copy = $array;
この直後は
$copy[0] = 1;
$copy[1] → とある変数空間 ← $array[1]
$copy[2] = 3; ↑
$ref
こんな感じ。
なので
$copy[0] = 'a';
$copy[1] = 'b';
$copy[2] = 'c';
と実行すると、「とある変数空間」の中身が「b」となって、結果、$array[1]が「b」になるということ。
これ知らなかったけど、これでいいのかPHPって感じの仕様ですね。
http://jp2.php.net/manual/ja/language.references.whatdo.php
参照代入で両辺がリファレンスに変換され、リファレンスが評価されると自動的にデリファレンスされるが、配列自体のコピーではデリファレンスされることなくリファレンス自身がコピーされる、と理解しました。
2012/06/05 21:56:59関数の引数処理やオブジェクトの関係でこんな仕様になってるんですかね。
ところで。
配列がリファレンスの集合体と言うのはどうかなと思います。
Cは連続した記憶域そのものですし、perlも実装はともかく連続した変数領域と見て良かったと思います。
pythonはそもそも変数自体がリファレンスですし。
というのは脇道ですけど。
パパトモさん、回答&長文に渡るコメント、ありがとうございます。
読ませてもらい、いろいろ考えたり実験した結果、
TransFreeBSDさんの言うように、
通常変数の代入の場合:
両辺がリファレンスになったあと、両辺デリファレンスされる
配列の要素の代入の場合:
両辺がリファレンスになったあと、左辺の変数だけデリファレンスされる
と考えるのが分かりやすそうです。
ちょっとphp -aで実験です。
まず、&を使うと参照型になるってこと。
でも、通常の参照代入で値が変化することはない。
ただ、おなじモノを指していることにはなる。
解釈としては変数に別名をあたえた、みたいな感じですかね。
ここまではわかりやすい。
本題はここから。
配列も作っただけなら中身は参照型ではない。
&をつけるだけなら別に中身が変わることはない。
ここで、配列の要素の代入を行うと、
その代入を行った要素が参照型に変化してしまう。
しかも、一度参照型になってしまうと、
再度その要素に代入しても参照型のままになってしまう。
では、配列の要素を配列の要素に参照代入すると?
このときは代入された配列の中身も参照型になる。
では、代入される側が配列の要素で、
代入する側が普通の変数の場合は。
やはり参照型で代入されている。
結局何だったのか、というと、
PHPは参照操作をするときは一旦参照型にして演算するけど、
最後に参照型から通常型に戻そうとする。(デリファレンス)
でも、配列の要素までは追っかけてデリファレンスできなくて、
そのまま参照型が残り続ける。
なんてところだと予想されます。
----
なんでこんなことに、と思ってしまいますが、
多分変数自体には「参照型」を作りたくなかったのではないか、と思います。
どんな変数も「箱」のようなもので、値を持っている。
なので、変数に参照型の値を代入しようとしても、基本的にはデリファレンスしてしまう。
デリファレンスの結果、同じ「箱」に別名がついたりする。
配列も、中身の実装はどうなってるかは置いておいて、
「箱」のなかに「箱」があるイメージで扱えるようにしておきたい、
というのがおそらく基本スタイルなんでしょう。
だから通常の配列の代入はコピー。
そこで、配列の要素である、「箱のなかの箱」を参照型にしたあと
デリファレンス(つまり別名をつける)しようとしても、
「箱のなかの箱」には名前をつけることができない。
だから仕方なく「参照型」ということにしておく。
つまり、デリファレンスを後回しにしている感じですね。
この「後回し」が奇妙な結果を産むわけですな。。。
きもちわるいなあ。
----
ちなみに。
関数への参照渡しに配列の要素を与えた場合だとこの珍現象は起きないのでした。
これは関数の実行が終われば、「箱のなかの箱」に別名を与えたものが解消されるから
「参照型」を用意する必要がないからかなーとか思いました。
グローバル変数使えば別名付けられるんじゃないか、と思って試してみましたが
そもそも、グローバル変数に参照代入してもグローバル変数には影響与えないみたいです。
こっちもかなり凶悪なハマりポイントですね。。。
----
2012/06/06 00:08:08以上をまとめると、
1.配列の要素の参照代入は特別な目的がない限りしないこと
2.グローバル変数に参照代入はしないこと
ってところでしょうかねぇ。
適当に実験して得られた結論なので、PHP詳しい人からのアドバイスがほしいです。