2010年7月17日土曜日

PHP: each と 配列カーソル

php で配列要素に関して反復処理を行うときに私は普段
foreach ($ar as $value)
foreach ($ar as &$value)
foreach ($ar as $key => $value)
を使いますが、気まぐれに each を使うことがあります。
while (list($key, $vlaue) = each($ar))
という使い方です。
でも、配列カーソルのことを意識していないと予想外の結果となるので注意が必要です。
reset() をループの直前に行う癖をつけておくと良いでしょう。
reset($ar);
while (list($key, $vlaue) = each($ar))
これは配列カーソルがどこにあるかを予想するのは意外に大変だからです。
例えば、
function func($ar) { // $ar は値渡し(参照渡しではない)
 while (list($key, $value) = each($ar)) {
  echo "$key => $value\n";
 }
}
という関数を作り、
$ar[0] = "a";
$ar[1] = "b";
$ar[2] = "c";
foreach ($ar as $a) {
 if ($a == "a") break; // 1つ目で止める
}

$ar2 = $ar; // コピーしてみる

func($ar2); // $ar2 を値渡し
を実行するとどうなるかわかりますか?
結果は、
1 => b
2 => c
です。
foreach はループの開始に配列カーソルを先頭要素にリセットしていますが、ループを抜けるときはそのままです。 それでも foreach だけ使用していれば、毎回最初にリセットされるのでループを抜けるときの配列カーソルを意識する必要はありません。
途中違う変数にコピーしていますが、このとき配列カーソルの位置も律儀にコピーされてしまいます。
func() を呼ぶときは値渡しなのですが、配列カーソルの位置も渡ってしまいます。
func() を記述している時点では、$ar の配列カーソルが先頭にないなんて予想しないでしょうが、実際には渡ってくる変数の配列カーソルの位置で結果が違ってしまいます。
ちなみに、foreach のループを途中で止めないと終了時に配列カーソルをリセットしてくれ、結果は
0 => a
1 => b
2 => c
となります。
以上のように配列カーソルの位置を見極めるにはその配列が使われる場所を追跡しないとなりません。
each() を利用する場合は不思議なことが起こらないように最初に reset() してからということですね。

0 件のコメント:

コメントを投稿