PHP 在 foreach 中使用 "引用" 的注意事项

PHP中使用引用是不好的,您应当尽量避免使用它们。现在有一个用例,会导致意外的行为:

该代码的输出是什么?

1
2
3
4
5
6
7
<?php
$a = array('a', 'b', 'c', 'd');

foreach ($a as &$v) { }
foreach ($a as $v) { }

print_r($a);

我们遍历数组两次而不做任何事情,因此结果应该没有改变。对吗?错!实际结果如下所示:

1
2
3
4
5
6
7
Array
(
[0] => a
[1] => b
[2] => c
[3] => c
)

为了理解为什么会发生这种情况,让我们回顾一下,看看PHP变量的实现方式以及引用的内容:

PHP变量由 标签(lable)容器(container) 组成。标签是哈希表中的条目(引擎中有一些优化,因此它并不总是在哈希表中),它可以表示函数的符号表,数组或对象的属性表。因此,我们有一个名称和一个指向容器的指针。容器内部称为 zval,用于存储值和一些元信息,该容器也可以是带有一组指向其他容器的标签的新哈希表,如果我们现在创建引用,则会导致第二个标签指向与另一个标签相同的容器。从那时起,这两个标签具有与容器相同的功能。

现在让我们分析上面的情况,它在图片中看起来像这样:

因此,我们有六个容器(全局符号表在顶部,一个容器包含一个名为 $a 的数组,左侧是一个容器,右侧是每个元素的一个容器)。
现在,我们开始第一次迭代:全局符号 $v 获取了一个新条目,并且 $v 成为对第一个数组元素的容器的引用:

因此,对 $a[0]$v 的更改将进入同一容器,对另一个容器会产生影响。
当迭代继续进行时,引用将断开,并且 $v 将成为对不同元素的引用。在迭代结束之后,$v 是对最后一个元素的引用:

请记住:$v 是一个引用,意味着对 $v 的任何更改都会影响其他引用,在这种情况下,为 $a[3],到目前为止,没有什么特别的事情发生。
但是现在第二次迭代开始了,这一步将当前元素的值分配给 $v,现在 $v 是对与 $a[3] 相同的元素的引用,因此通过为 $v 赋值,$a[3] 也将更改:

继续后续步骤:

现在我们可以很容易地猜测到最后一步会发生什么:$v 被分配了最后一个元素的值 $a[3],并且由于 $a[3] 是对 $v 的引用,因此它将自己分配给自己,什么也没发生:

这就是我们在上面看到的结果。

因此:请注意引用!它们会产生非常奇怪的效果。

想避免此类问题,可以使用不同的变量名,但这不是一个好办法,这为你的代码埋下了隐患;
或者使用 unset() 释放变量。
但我认为您应当尽量避免使用它们。

评论