2012年3月15日木曜日

for文とforeach文の不思議な違い

こんにちは、matsuiです。

今回ご紹介する内容は、私が実際にPHPにてソーシャルアプリを実装していく際に挙動確認のために作ったforとforeachのサンプルソースです。

出だしから既に内容について推測出来てしまった方もいらっしゃるとは思いますが、
ここでは「なぜ今更forとforeachの動きを確認しているの?」と思って頂いたと仮定して話を進めさせていただきたいと思います。

forとforeachを使ったサンプルプログラムを3パターンほどご用意しました。
以下のソースをご覧ください。

// パターンA
$arrayList = array(
    0,
    1,
    2
);

foreach ($arrayList as $key => $value) {
    array_pop($arrayList);
    var_dump($arrayList);
}

// パターンB
$arrayList = array(
    0,
    1,
    2
);

$length = count($arrayList);
for ($i = 0;$i < $length;$i++) {
    array_pop($arrayList);
    var_dump($arrayList);
}

// パターンC
$arrayList = array(
    0,
    1,
    2
);

for ($i = 0;$i < count($arrayList);$i++) {
    array_pop($arrayList);
    var_dump($arrayList);
}

それぞれの違いについてお分かりでしょうか?
配列の宣言は全て同じで、array_pop関数を使って宣言した配列の最後の要素を取り出した後、配列の中身をvar_dumpを使って出力しています。
違いはforかforeachで処理しているかと配列の長さをどこで取得しているかの違いです。
各パターンの出力結果の違いについて想像してみてください。

出力結果は以下。

パターンAの結果
array(2) {
[0]=>
int(0)
[1]=>
int(1)
}

array(1) {
[0]=>
int(0)
}

array(0) {
}

パターンBの結果
array(2) {
[0]=>
int(0)
[1]=>
int(1)
}

array(1) {
[0]=>
int(0)
}

array(0) {
}

パターンCの結果
array(2) {
[0]=>
int(0)
[1]=>
int(1)
}

array(1) {
[0]=>
int(0)
}

出力結果は想像通りだったでしょうか?
結果だけ見るのであればパターンAとパターンBは同じです。
パターンCだけが結果が違うのは、配列の要素数をどのタイミングで取得し、終了条件と比較しているかという点が他と違うためです。

さて、そろそろ本題に入ります。
以下のような2つのプログラム、パターンDとパターンEがあります。

// パターンD
$arrayList = array(
    0,
    1,
    2
);

foreach ($arrayList as $key => $value) {
    $popValue = array_pop($arrayList);
    array_unshift($arrayList, $popValue);
    var_dump('key : ' . $key . " value :" . $value);
    var_dump($arrayList);
}

// パターンE
$arrayList = array(
    0,
    1,
    2
);

$length = count($arrayList);
for ($i = 0;$i < $length;$i++) {
    $popValue = array_pop($arrayList);
    array_unshift($arrayList, $popValue);
    var_dump('key : ' . $i . " value :" . $arrayList[$i]);
    var_dump($arrayList);
}

今までの例と同じように配列の宣言は一緒です。
違うのはforとforeachの構文とそれに伴う配列の要素に対するアクセス方法の違いだけで、「配列の最後の要素を取得し、素配列の最初の要素として登録する」という動き自体は一緒です。
どんな実行結果が出るか想像してみてください。

実行結果は以下です。

パターンD
string(16) "key : 0 value :0"
array(3) {
[0]=>
int(2)
[1]=>
int(0)
[2]=>
int(1)
}

string(16) "key : 1 value :1"
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(0)
}

string(16) "key : 2 value :2"
array(3) {
[0]=>
int(0)
[1]=>
int(1)
[2]=>
int(2)
}

パターンE
string(16) "key : 0 value :2"
array(3) {
[0]=>
int(2)
[1]=>
int(0)
[2]=>
int(1)
}

string(16) "key : 1 value :2"
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(0)
}

string(16) "key : 2 value :2"
array(3) {
[0]=>
int(0)
[1]=>
int(1)
[2]=>
int(2)
}

実行前に想像した結果と実際の実行結果が違った方も、いらっしゃるのではないかと思います。
パターンDは、実際の宣言した配列の出力内容とforeach構文によって取得した要素($keyと$value)の中身が違っています。

どうしてこのような結果の違いが生まれるのでしょうか。

PHPのソースコードを読めば明確な答えが出るのでしょうが、ちょっと時間的にそうも言ってられないので、
プログラミングPHP(第2版)という本からその原因と思われる個所についての説明を拝借してくることにします。

foreachは配列自体を操作するわけではなく、配列のコピーを作成して処理します。foreachループ内で配列の要素を削除したり挿入したりしても、ループではその要素を反映しません。

だそうです。
配列の確かにコピーを作成して処理した場合の動きだと考えると、動作結果に納得がいきます。

特に意識をして使い分けをするようなケースは余りないかもしれませんが、forとforeachには明確な違いがあるということが実験から明らかになりました。

残念ながら内部動作まで私自身が読み解いたわけではありませんので、「違いがある」という結論以上のことを明言できないのが残念ですが、今回はここまでとさせてください。

0 件のコメント:

コメントを投稿