これ何て呼びますか

<?php

$array = array(1,2,3);
var_dump($array);
echo "<br>";

$ref = &$array[1];
var_dump($array);
echo "<br>";

$copy = $array;
$copy[0] = 'a';
$copy[1] = 'b';
$copy[2] = 'c';
var_dump($array);
echo "<br>";

?>

上記のコードの結果が、
以下の内容になる理由を教えてください。

array(3) { [0]=> int(1) [1]=> int(2) [2]=> int(3) }
array(3) { [0]=> int(1) [1]=> &int(2) [2]=> int(3) }
array(3) { [0]=> int(1) [1]=> &string(1) "b" [2]=> int(3) }


(A)
PHPが糞言語なのはどう考えても参照をポインタだと思っているお前らが悪い - なんたらノート第三期ベータ
http://tanakahisateru.hatenablog.jp/entry/2013/1 …

(B)
PHPとかいう糞言語|いんまのブログ
http://ameblo.jp/nikko-inma/entry-11122429825.html

上記の現象は(B)のブログで紹介されていて、この現象それをもってPHPをクソ言語だといっています。で、そのブログを読んで激怒した人が、(A)のブログで「クソ言語だと思う奴が悪い」という趣旨でPHPの擁護をしようとしています。しかし、パフォーマンスがどうとかメモリの使用量がどうとか的外れな説明が並び、挙句の果てには「&は使うな」という結論に至っており、なぜ上記の結果になるのかの理由は全く示されていません。その結論を普通に解釈したら、「使ってはならない」機能を提供しているクソ言語、という事になりかねません…。


この両者のブログの争いはさておき、「$ref = &$array[1];」などと書いてしまうと後続の処理で期待と異なる結果になる理由が分かりません。常識的に考えて「おかしい挙動になるから使わないで」などという機能であるはずはなく、意味があるから用意されているのだと思います。

私の知る限り、関数の実行結果をreturnとは別に受け取りたい場合に、その関数の定義の引数に使用しているケースがあります。例えばstr_replaceの第四引数。このような使い方はPHPに限らず、非オブジェクト指向の言語でしばしばみかけるように思います。結果を受け取る変数を引数で指定するのは私には気持ち悪いのですが、ビルトイン関数で利用されている使用例ですので、PHPはそういう言語だという事だと思ってあきらめます。

str_replaceのようなケースは結果が期待通りになり、何も不思議な事はありません。しかし、それ以外にはどうなんだと。引数以外には使ってはならないんだったら使えないようにしておくべきだし、そうなってはおらず自由に使えるようになっているのだから何か意味があるはずではないかと思います。


■質問は以下です。
・上記のコードの結果なぜ「$array[1]だけがbになる」のでしょうか?
・そうなる意義や有用性は?
・&はどういうときに使用すべきですか。使用すべきケースはありますか?
 (関数の結果を受け取るために引数で指定する場合以外で)

よろしくお願いします。

A 回答 (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);
    • good
    • 0
この回答へのお礼

>3. $array[1] はis_ref=1 なので、$copy[1] に対してエイリアスの作成を行う。

なるほど。ここがキモなんでしょうね。
普通の感覚だったら「エイリアスの作成は&をつけたときだけ」にしてほしいと期待すると思います。しかしis_ref=1になっているという理由で後続の処理にまで影響してしまうのですね…。

そんな仕様だったら、安全な関数を作るためには、is_refの状態を一つ一つ確認しなければならないとかそういう話になりそうな気もします。なのでもしかしたら言語の仕様バグなんじゃないかとすら思うようになりました。

それが良いか悪いか、バグかバグじゃないかはさておき、今のPHPではそうなっているということなのですね。


オブジェクトの参照に関しては
・オブジェクトの内容を変更しようとする場合には変わらない
・オブジェクトそのものを置き換える場合には変わる
ということですね。おおむね理解しました。


まだどうも腑に落ちない部分もあるものの、だいぶ理解できました。
参考になりました。ありがとうございました。

お礼日時:2014/10/05 15:49

>> 上記のコードの結果なぜ「$array[1]だけがbになる」のでしょうか?



PHPの通称参照渡しを参照渡しと呼ぶ方が悪い気がする…他の言語の参照渡しは一方向の参照だと思いますが、PHPのはただのエイリアスの作成です。

参照カウント法の原理については公式マニュアルにも説明があります。

PHP Manual - 参照カウント法の原理
http://php.net/manual/ja/features.gc.refcounting …

オブジェクトの参照に関してだけは少しややこしいので以下で補足的に記事を書いてみました。ただし、コピーオンライトについては隠蔽されている処理とみなしてあえて触れていません。

オブジェクトと参照
http://qiita.com/mpyw/items/41230bec5c02142ae691 …

多分あなたの感覚からすれば

「配列の "変数" に参照がかかると、子要素にも継承するように参照が適用される。でも参照をかけられるのは "変数" のみだ!子要素の1つだけに参照がかけられるのはおかしい!」

ということなんだと思いますが、PHPは「配列要素」と「変数」をシンボルテーブル上では一切区別していないことにお気づきでしょうか。「$GLOBALS」といった特殊な変数、「get_defined_vars」「compact」「extract」といった特殊な関数は奇妙に思えますが、こういう実装になっているからこそ実現できる機能なのです。

「シンボルテーブル上のキーに対して参照がかかると、その値以下が参照するシンボルテーブル上にも継承するように参照が適用される。逆にそのキーが参照 "される" シンボルテーブルに対しては何の影響も与えない。」

こういう理解が正しいと思います。

>> &はどういうときに使用すべきですか。

この回答したときに使いましたね、まぁこんな目的滅多にないとは思いますが↓
http://stackoverflow.com/questions/19203988/stri …

(わざわざ1回array_sliceしてるのは配列のコピーを作るためですね)

この回答への補足

ただ、よく考えると最初の現象がよく分かりません。

$ref = &$array[1];

・エイリアスを作った
・「$ref」は「$array[1]」の別名のようなもの
ということですよね。

だから「$ref」を変更したら「$array[1]」も変わる、と言うのなら話はわかります。もしくは「$copy[1] = &$array[1];」などとしているのであれば、これも話は分かります。しかし、「$copy[1] = &$array[1];」という処理はなく、値を設定しているのは「$ref」ではなく「$copy[1]」です。この点がどうも腑に落ちません。

「is_ref」フラグが立ってしまうと、&で代入した変数だけでなく他にも影響してしまうということなのでしょうかね…。

この点についてもしも比較的簡単に説明できる内容なら追加で教えていただきたいです。
そうでないのなら、別途質問を立てようと思いますのでその旨をお知らせください。

よろしくお願いします。

補足日時:2014/10/04 17:33
    • good
    • 0
この回答へのお礼

>PHPの通称参照渡しを参照渡しと呼ぶ方が悪い気がする…
>他の言語の参照渡しは一方向の参照だと思いますが、
>PHPのはただのエイリアスの作成です。

なるほど!この言葉でかなりスッキリしました。その観点で言葉をきちんと区別して読んでみると、PHPの公式リファレンスでは「参照渡し」という言葉は一切使用していない事が分かりました。すべて「リファレンス渡し」としており、一般的な意味での「参照渡し」とは違うのだよというニュアンスがあるのかなと思いました。(一方で(A)のブログでは「参照渡し」という言葉を連呼しおり、違いを区別できていないようでした。そんな誰が書いたか分からないようなブログを参考にしようとした私が悪いのかもしれませんが…)

なので「なぜ参照渡しなのにこんな結果になるんだ」という私の最大の疑問は解消しました。そもそも参照渡しと似て非なる仕様なのだと一旦理解する事にしました。


また、一応こちらも読んでいたのですが、

PHP: オブジェクトと参照 - Manual
http://php.net/manual/ja/language.oop5.reference …

最初は結果的に何が違うのか全く分かりませんでした。しかし教えていただいた参照カウントの情報も読んだことにより、is_refが変わるという事が分かり、少なくとも内部的な情報の持ち方が異なるという事ははっきりと確認・認識できました。(ただ、その結果何がどう異なるのかはよく分かりませんが)

まだ十分な理解はしていませんが、大きく前進しました。
ありがとうございました

お礼日時:2014/10/04 15:01

#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);
?>

これが便利かどうか・どちらがわかりやすいかは意見がわかれるところですが、
いろいろな書き方ができるというのは悪いことではないと思います。
いやなら使わなければいいだけの話なので
    • good
    • 0
この回答へのお礼

>参照渡ししない場合は添え字を使う必要があります。

私がforeachを使う場合はこちらしか使った事がありませんでした。連想配列として使う場合には、keyとvalueをセットで使う処理しか書いていないので。というのも趣味で今年4月からPHPを始めたので、まだ経験が浅いからというのもありますけど。


>いろいろな書き方ができるというのは悪いことではないと思います。

そうですね。それにforeachの書き方の例として、公式のリファレンスにもある書き方ですので、自分が使うかどうかの前に人が書いたソースを理解するためにも必要ですからね。

PHP: foreach - Manual
http://php.net/manual/ja/control-structures.fore …

参考になりました。
ありがとうございました

お礼日時:2014/10/03 23:28

使い方さえ間違わなければ便利な機能だと思いますけどね


たとえば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で上記の書き方が出来るというお話は参考になりました。
が、「$z=&$w[];」の部分はさっぱり分かりませんでした。
これはどういう意味なのでしょうか?

もしよろしければこれについても教えてください。
よろしくお願いします。

補足日時:2014/10/03 23:29
    • good
    • 0
この回答へのお礼

一つ目の「foreach($x as &$y){」は、私ならこう書くので
# for ($i = 0; $i < count($x); $i++) {
#   $x[$i]["b"] = 10;
# }
特に困った事はないのですが、このような記述も出来るのですね。
なるほど。ありがとうございました。

お礼日時:2014/10/03 19:34

個人的な見解だから鵜呑みにしないでね



使用すべきケースが見つからない。
今までの開発でも使ったことが無い。

$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)の時点で、状態が異なる理由です。

よろしくお願いします。

補足日時:2014/10/03 19:29
    • good
    • 0
この回答へのお礼

>使用すべきケースが見つからない。

なるほど。ありがとうございました

お礼日時:2014/10/03 19:29

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!


おすすめ情報