foreach 支持在遍历时以引用方式修改数组,这个特性很好用,但它藏着一个我亲身踩过的坑。

先看正常用法,把每个元素翻倍,结果是正确的:

$a = [1, 2, 3];
foreach ($a as &$x) {
    $x = $x * 2;
}
unset($x);
// $a 是 [2, 4, 6]

注意这里的 unset($x) 不是可有可无的。循环结束后 $x 并没有被销毁,它仍然以引用方式指向 $a 的最后一项。如果不 unset,接着再写一个普通的 foreach,就会出问题:

$a = [1, 2, 3];
foreach ($a as &$x) {}
foreach ($a as $x) {}
// $a 变成了 [1, 2, 2],不是 [1, 2, 3]

原因是第二个循环里,每次给 $x 赋值,实际都写进了 $x 所指向的 $a[2]:第一次把 $a[2] 写成 1,第二次写成 2,第三次读到的是刚被改过的 $a[2] 自己,于是最终得到 [1, 2, 2]。

结论只有一条:凡是用了 foreach 引用赋值,循环完立刻 unset。也可以干脆避开引用,用 array_map 或者再建一个数组来装结果。