PHPの参照代入に関してです。

PHPを勉強し始めたのですが、RubyやPythonといった言語と変数・配列の扱いが異なって戸惑っています。
一番わからないのが参照代入です。
http://www.programming-magic.com/20080307090613/
このサイトで挙動が解説されてますが、イマイチ納得できません。
PHPの変数の参照代入に関して詳しく解説しているサイトを教えてください。

回答の条件
  • URL必須
  • 1人5回まで
  • 登録:
  • 終了:2012/06/10 14:44:02
※ 有料アンケート・ポイント付き質問機能は2023年2月28日に終了しました。

ベストアンサー

id:papa-tomo No.1

回答回数362ベストアンサー獲得回数107

ポイント300pt

あー、これ分かりにくいね。

$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

他8件のコメントを見る
id:TransFreeBSD

参照代入で両辺がリファレンスに変換され、リファレンスが評価されると自動的にデリファレンスされるが、配列自体のコピーではデリファレンスされることなくリファレンス自身がコピーされる、と理解しました。
関数の引数処理やオブジェクトの関係でこんな仕様になってるんですかね。

ところで。
配列がリファレンスの集合体と言うのはどうかなと思います。
Cは連続した記憶域そのものですし、perlも実装はともかく連続した変数領域と見て良かったと思います。
pythonはそもそも変数自体がリファレンスですし。
というのは脇道ですけど。

2012/06/05 21:56:59
id:kabisuke

パパトモさん、回答&長文に渡るコメント、ありがとうございます。

読ませてもらい、いろいろ考えたり実験した結果、
TransFreeBSDさんの言うように、

通常変数の代入の場合:
両辺がリファレンスになったあと、両辺デリファレンスされる
配列の要素の代入の場合:
両辺がリファレンスになったあと、左辺の変数だけデリファレンスされる

と考えるのが分かりやすそうです。

ちょっとphp -aで実験です。
まず、&を使うと参照型になるってこと。

php > $a = 1;
php > var_dump($a);
int(1)
php > var_dump(&$a);
&int(1)


でも、通常の参照代入で値が変化することはない。
ただ、おなじモノを指していることにはなる。
解釈としては変数に別名をあたえた、みたいな感じですかね。
ここまではわかりやすい。

php > $a = 1;
php > $b = &$a;
php > var_dump($a);
int(1)
php > var_dump($b);
int(1)
php > $b = 2;
php > var_dump($a);
int(2)
php > var_dump($b);
int(2)


本題はここから。
配列も作っただけなら中身は参照型ではない。
&をつけるだけなら別に中身が変わることはない。

php > $arr = array(1);
php > var_dump($arr);
array(1) {
[0]=>
int(1)
}
php > var_dump($arr[0]);
int(1)
php > var_dump(&$arr[0]);
&int(1)
php > var_dump($arr[0]);
int(1)


ここで、配列の要素の代入を行うと、
その代入を行った要素が参照型に変化してしまう。
しかも、一度参照型になってしまうと、
再度その要素に代入しても参照型のままになってしまう。

php > $arr = array(1);
php > $tmp = &$arr[0];
php > var_dump($arr);
array(1) {
[0]=>
&int(1)
}
php > var_dump($tmp);
int(1)
php > $arr[0] = 'a';
php > var_dump($arr);
array(1) {
[0]=>
&string(1) "a"
}


では、配列の要素を配列の要素に参照代入すると?
このときは代入された配列の中身も参照型になる。

php > $arr1 = array(1);
php > $arr2 = array('a');
php > $arr2[0] = &$arr1[0];
php > var_dump($arr1);
array(1) {
[0]=>
&int(1)
}
php > var_dump($arr2);
array(1) {
[0]=>
&int(1)
}


では、代入される側が配列の要素で、
代入する側が普通の変数の場合は。

php > $arr = array(1);
php > $a = 'a';
php > $arr[0] = &$a;
php > var_dump($a);
string(1) "a"
php > var_dump($arr);
array(1) {
[0]=>
&string(1) "a"
}

やはり参照型で代入されている。

結局何だったのか、というと、
PHPは参照操作をするときは一旦参照型にして演算するけど、
最後に参照型から通常型に戻そうとする。(デリファレンス)
でも、配列の要素までは追っかけてデリファレンスできなくて、
そのまま参照型が残り続ける。

なんてところだと予想されます。

----

なんでこんなことに、と思ってしまいますが、
多分変数自体には「参照型」を作りたくなかったのではないか、と思います。
どんな変数も「箱」のようなもので、値を持っている。
なので、変数に参照型の値を代入しようとしても、基本的にはデリファレンスしてしまう。
デリファレンスの結果、同じ「箱」に別名がついたりする。

配列も、中身の実装はどうなってるかは置いておいて、
「箱」のなかに「箱」があるイメージで扱えるようにしておきたい、
というのがおそらく基本スタイルなんでしょう。
だから通常の配列の代入はコピー。

そこで、配列の要素である、「箱のなかの箱」を参照型にしたあと
デリファレンス(つまり別名をつける)しようとしても、
「箱のなかの箱」には名前をつけることができない。
だから仕方なく「参照型」ということにしておく。

つまり、デリファレンスを後回しにしている感じですね。

この「後回し」が奇妙な結果を産むわけですな。。。
きもちわるいなあ。

----

ちなみに。
関数への参照渡しに配列の要素を与えた場合だとこの珍現象は起きないのでした。
これは関数の実行が終われば、「箱のなかの箱」に別名を与えたものが解消されるから
「参照型」を用意する必要がないからかなーとか思いました。

php > function hoge(&$a){ $a = 123; }
php > $b = 24;
php > hoge($b);
php > var_dump($b);
int(123)
php > $arr = array(1);
php > hoge($arr[0]);
php > var_dump($arr);
array(1) {
[0]=>
int(123)
}


グローバル変数使えば別名付けられるんじゃないか、と思って試してみましたが
そもそも、グローバル変数に参照代入してもグローバル変数には影響与えないみたいです。
こっちもかなり凶悪なハマりポイントですね。。。

php > $g = "default";
php > function hoge(){global $g; $a = 1; $g = &$a; $a = 10; }
php > hoge();
php > var_dump($g);
string(7) "default"


----

以上をまとめると、
1.配列の要素の参照代入は特別な目的がない限りしないこと
2.グローバル変数に参照代入はしないこと
ってところでしょうかねぇ。


適当に実験して得られた結論なので、PHP詳しい人からのアドバイスがほしいです。

2012/06/06 00:08:08
  • id:kabisuke
    <?php
    $arr1 = array(0,1);
    $tmp = &$arr1[0];
    $arr2 = $arr1;
    $arr2[0] = 'a';
    $arr2[1] = 'b';
    var_dump($arr1);
    ?>

    これと、

    <php?
    $a = 0;
    $tmp = &$a;
    $b = $a;
    $b = 'a';
    var_dump($a);
    ?>

    の挙動の違いに関して説明できるのがベストです。
    質問時に挙げたサイトの説明だと下の例が0を出力する説明ができません。
  • id:standard_one
    この挙動は知らなかった
    つーか、わかりにくぅw

    下の例は $a がリファレンスになってて0が保持されている
    $bは一瞬$aと同じリファレンスを持つけど、そのあと'a'というリテラルで$bが上書きされている
    PHPの変数の保持方法は知らないけど、ディスクリプタみたいな方法で変数を管理していると考えたら下の例では$bの変数の保持方法ごと上書きされただけで$aの方には何も影響がないってことじゃないかな
    ポインタみたいにアドレスと内容が1対1だという先入観があるとめっちゃ理解しにくいと思う
    変数が直接値を持つわけではない(と仮定して)考えてみると受け入れやすいと思いますよ
    繰り返しになりますが私はPHPの変数保持方法を知りませんけどw
  • id:kabisuke
    コメントありがとうございます。

    ポインタっぽいモノを導入しようとして
    でもポインタ型を作りたくないから
    無理やり混ぜたら変な事になりました、
    って感じがしますよね…

    ただ、上の例だと参照代入を行った瞬間に、
    配列の中身が参照型っぽいモノに変化します。
    対し、下は元の変数には影響を与えません。
    なんとなく配列の要素に対して参照演算すると、中身が変わるような気がします。

    よくわからないので引き続き有識者のコメント、回答を募集です。
  • id:kabisuke
    パパトモさん以外からの回答がございませんでしたので、パパトモさんにポイントを差し上げます。

    結局、参考になるサイトはあまり挙がりませんでしたが、phpの変数は基本的に値の格納を行うという説明で何とか説明が付けられそうです。
  • id:papa-tomo
    私もはっきりとした理由が知りたかったので、ベストアンサーに選ばれて恐縮しています。

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

回答リクエストを送信したユーザーはいません