- ベストアンサー
&を使ったときの挙動
- PHPの参照渡しの仕組みに関する質問について、以下のコードの結果や意義について説明します。
- コードの結果は、$array[1]が'b'になる理由を説明しています。
- 参照渡しの意義や有用性、そして&の使用すべきケースについても説明します。
- みんなの回答 (5)
- 専門家の回答
質問者が選んだベストアンサー
- ベストアンサー
>> 「is_ref」フラグが立ってしまうと、&で代入した変数だけでなく他にも影響してしまうということなのでしょうかね…。 $copy = $array; で行われる処理は、内部的には以下のようになっているはずです。 (※コピーオンライトについては説明を簡単にするために無視することにします) 1. $arrayは is_ref=0 なので、$copy に対してコピーを行う。これは配列であるので子要素にコピー処理が続く。 2. $array[0] はis_ref=0 なので、$copy[0] に対してコピーを行う。 3. $array[1] はis_ref=1 なので、$copy[1] に対してエイリアスの作成を行う。(※$arrayのis_refには左右されない) 4. $array[2] はis_ref=0 なので、$copy[2] に対してコピーを行う。 $copy = &$array; 参考までにこちらについても… 1. $arrayは is_ref=1 なので、$copy に対してエイリアスの作成を行う。 以上です。 >> ただ、その結果何がどう異なるのかはよく分かりませんが 以下の2つの結果は変わりません。 $a = new stdClass; $a->foo = 'bar'; $b = $a; $b->foo = 'baz'; var_dump($a); $a = new stdClass; $a->foo = 'bar'; $b = &$a; $b->foo = 'baz'; var_dump($a); 以下の2つの結果は変わります。 $a = new stdClass; $a->foo = 'bar'; $b = $a; $b = null; var_dump($a); $a = new stdClass; $a->foo = 'bar'; $b = &$a; $b = null; var_dump($a);
その他の回答 (4)
>> 上記のコードの結果なぜ「$array[1]だけがbになる」のでしょうか? PHPの通称参照渡しを参照渡しと呼ぶ方が悪い気がする…他の言語の参照渡しは一方向の参照だと思いますが、PHPのはただのエイリアスの作成です。 参照カウント法の原理については公式マニュアルにも説明があります。 PHP Manual - 参照カウント法の原理 http://php.net/manual/ja/features.gc.refcounting-basics.php オブジェクトの参照に関してだけは少しややこしいので以下で補足的に記事を書いてみました。ただし、コピーオンライトについては隠蔽されている処理とみなしてあえて触れていません。 オブジェクトと参照 http://qiita.com/mpyw/items/41230bec5c02142ae691#2-9 多分あなたの感覚からすれば 「配列の "変数" に参照がかかると、子要素にも継承するように参照が適用される。でも参照をかけられるのは "変数" のみだ!子要素の1つだけに参照がかけられるのはおかしい!」 ということなんだと思いますが、PHPは「配列要素」と「変数」をシンボルテーブル上では一切区別していないことにお気づきでしょうか。「$GLOBALS」といった特殊な変数、「get_defined_vars」「compact」「extract」といった特殊な関数は奇妙に思えますが、こういう実装になっているからこそ実現できる機能なのです。 「シンボルテーブル上のキーに対して参照がかかると、その値以下が参照するシンボルテーブル上にも継承するように参照が適用される。逆にそのキーが参照 "される" シンボルテーブルに対しては何の影響も与えない。」 こういう理解が正しいと思います。 >> &はどういうときに使用すべきですか。 この回答したときに使いましたね、まぁこんな目的滅多にないとは思いますが↓ http://stackoverflow.com/questions/19203988/string-tree-separated-only-with-commas-to-an-multidimensional-php-array/19204464#19204464 (わざわざ1回array_sliceしてるのは配列のコピーを作るためですね)
お礼
>PHPの通称参照渡しを参照渡しと呼ぶ方が悪い気がする… >他の言語の参照渡しは一方向の参照だと思いますが、 >PHPのはただのエイリアスの作成です。 なるほど!この言葉でかなりスッキリしました。その観点で言葉をきちんと区別して読んでみると、PHPの公式リファレンスでは「参照渡し」という言葉は一切使用していない事が分かりました。すべて「リファレンス渡し」としており、一般的な意味での「参照渡し」とは違うのだよというニュアンスがあるのかなと思いました。(一方で(A)のブログでは「参照渡し」という言葉を連呼しおり、違いを区別できていないようでした。そんな誰が書いたか分からないようなブログを参考にしようとした私が悪いのかもしれませんが…) なので「なぜ参照渡しなのにこんな結果になるんだ」という私の最大の疑問は解消しました。そもそも参照渡しと似て非なる仕様なのだと一旦理解する事にしました。 また、一応こちらも読んでいたのですが、 PHP: オブジェクトと参照 - Manual http://php.net/manual/ja/language.oop5.references.php 最初は結果的に何が違うのか全く分かりませんでした。しかし教えていただいた参照カウントの情報も読んだことにより、is_refが変わるという事が分かり、少なくとも内部的な情報の持ち方が異なるという事ははっきりと確認・認識できました。(ただ、その結果何がどう異なるのかはよく分かりませんが) まだ十分な理解はしていませんが、大きく前進しました。 ありがとうございました
補足
ただ、よく考えると最初の現象がよく分かりません。 $ref = &$array[1]; で ・エイリアスを作った ・「$ref」は「$array[1]」の別名のようなもの ということですよね。 だから「$ref」を変更したら「$array[1]」も変わる、と言うのなら話はわかります。もしくは「$copy[1] = &$array[1];」などとしているのであれば、これも話は分かります。しかし、「$copy[1] = &$array[1];」という処理はなく、値を設定しているのは「$ref」ではなく「$copy[1]」です。この点がどうも腑に落ちません。 「is_ref」フラグが立ってしまうと、&で代入した変数だけでなく他にも影響してしまうということなのでしょうかね…。 この点についてもしも比較的簡単に説明できる内容なら追加で教えていただきたいです。 そうでないのなら、別途質問を立てようと思いますのでその旨をお知らせください。 よろしくお願いします。
- yambejp
- ベストアンサー率51% (3827/7415)
#2です。 >私ならこう書くので わたしの例示したものが悪かったのですが 配列は必ずしも0から1ずつ進むわけではないのでforeachを使う前提で 考えてみてください。 <?php $x1=array(10=>array("a"=>1),20=>array("a"=>2),30=>array("a"=>3)); foreach($x1 as &$y1){ $y1["b"]=10; } print_r($x1); ?> を参照渡ししない場合は添え字を使う必要があります。 <?PHP $x2=array(10=>array("a"=>1),20=>array("a"=>2),30=>array("a"=>3)); foreach($x2 as $key=>$y2){ $x2[$key]["b"]=10; } print_r($x2); ?> これが便利かどうか・どちらがわかりやすいかは意見がわかれるところですが、 いろいろな書き方ができるというのは悪いことではないと思います。 いやなら使わなければいいだけの話なので
お礼
>参照渡ししない場合は添え字を使う必要があります。 私がforeachを使う場合はこちらしか使った事がありませんでした。連想配列として使う場合には、keyとvalueをセットで使う処理しか書いていないので。というのも趣味で今年4月からPHPを始めたので、まだ経験が浅いからというのもありますけど。 >いろいろな書き方ができるというのは悪いことではないと思います。 そうですね。それにforeachの書き方の例として、公式のリファレンスにもある書き方ですので、自分が使うかどうかの前に人が書いたソースを理解するためにも必要ですからね。 PHP: foreach - Manual http://php.net/manual/ja/control-structures.foreach.php 参考になりました。 ありがとうございました
- yambejp
- ベストアンサー率51% (3827/7415)
使い方さえ間違わなければ便利な機能だと思いますけどね たとえばforeachで参照だけでなく代入までおこなったり (SQL処理など)ループ内で新しい配列をおこすときに使ったりしてます もちろん代用処理もあるのでマストな機能ではありませんが・・・ <pre> <?php $x=array(array("a"=>1),array("a"=>2),array("a"=>3)); foreach($x as $y){ $y["b"]=10; } print_r($x);//変化ない foreach($x as &$y){ $y["b"]=10; } print_r($x);//セットされる foreach($x as $y){ $z=&$w[]; $z["c"]=100; $z=array_merge($z,$y); $z["d"]=200; } print_r($w);//新しい配列をつくる ?> </pre>
お礼
一つ目の「foreach($x as &$y){」は、私ならこう書くので # for ($i = 0; $i < count($x); $i++) { # $x[$i]["b"] = 10; # } 特に困った事はないのですが、このような記述も出来るのですね。 なるほど。ありがとうございました。
補足
foreachで上記の書き方が出来るというお話は参考になりました。 が、「$z=&$w[];」の部分はさっぱり分かりませんでした。 これはどういう意味なのでしょうか? もしよろしければこれについても教えてください。 よろしくお願いします。
- Taiyonoshizuku
- ベストアンサー率37% (183/489)
個人的な見解だから鵜呑みにしないでね 使用すべきケースが見つからない。 今までの開発でも使ったことが無い。 $array[1]だけがbになるのは$array[1]を参照渡しにしちゃったから $copy[0]は$array[0]のピーコ $copy[2]も$array[2]のピーコ $copy[1]は$array[1]のアドレス(FF番地としましょう) だから$copy[1](FF番地)="b"とすると メモリ内のFF番地がbになる。そこは$array[1]の場所だから~
お礼
>使用すべきケースが見つからない。 なるほど。ありがとうございました
補足
> $array[1]だけがbになるのは$array[1]を参照渡しにしちゃったから まぁそうなんでしょうけど、他の言語だったら「参照渡しをしただけ」だったら、状態は変わりませんよね。でもPHPでは変わってしまうわけです。それがなぜなのかという事です。この他の言語と違うの部分について、もし可能なら説明していただきたいです。 # $array = array(1,2,3); // (1) # $ref = &$array[1]; // (2) この2行だけで、(1)の時点と(2)の時点で、状態が異なる理由です。 よろしくお願いします。
お礼
>3. $array[1] はis_ref=1 なので、$copy[1] に対してエイリアスの作成を行う。 なるほど。ここがキモなんでしょうね。 普通の感覚だったら「エイリアスの作成は&をつけたときだけ」にしてほしいと期待すると思います。しかしis_ref=1になっているという理由で後続の処理にまで影響してしまうのですね…。 そんな仕様だったら、安全な関数を作るためには、is_refの状態を一つ一つ確認しなければならないとかそういう話になりそうな気もします。なのでもしかしたら言語の仕様バグなんじゃないかとすら思うようになりました。 それが良いか悪いか、バグかバグじゃないかはさておき、今のPHPではそうなっているということなのですね。 オブジェクトの参照に関しては ・オブジェクトの内容を変更しようとする場合には変わらない ・オブジェクトそのものを置き換える場合には変わる ということですね。おおむね理解しました。 まだどうも腑に落ちない部分もあるものの、だいぶ理解できました。 参考になりました。ありがとうございました。