PHP Foreach 引用传递: 最后一个元素复制? (错误?)

我只是在写一个简单的 php 脚本时,出现了一些非常奇怪的行为。我把它缩小到了重现错误所需的最低限度:

<?php


$arr = array("foo",
"bar",
"baz");


foreach ($arr as &$item) { /* do nothing by reference */ }
print_r($arr);


foreach ($arr as $item) { /* do nothing by value */ }
print_r($arr); // $arr has changed....why?


?>

产出:

Array
(
[0] => foo
[1] => bar
[2] => baz
)
Array
(
[0] => foo
[1] => bar
[2] => bar
)

这是一个错误,还是一些真正奇怪的行为,应该发生?

13439 次浏览

在第一个 foreach 循环之后,$item仍然是对某个值的引用,这个值也被 $arr[2]使用。因此,第二个循环中的每个 foreach 调用(不通过引用进行调用)都用新值替换该值,从而替换 $arr[2]

所以循环1,值和 $arr[2]变成 $arr[0],也就是‘ foo’。
循环2中,值和 $arr[2]变成 $arr[1],即“ bar”。
循环3中,值和 $arr[2]变成 $arr[2],即“ bar”(因为循环2)。

值“ baz”实际上在第二个 foreach 循环的第一次调用时丢失。

调试输出

对于循环的每次迭代,我们将回显 $item的值并递归地打印数组 $arr

当第一个循环运行完成时,我们看到如下输出:

foo
Array ( [0] => foo [1] => bar [2] => baz )


bar
Array ( [0] => foo [1] => bar [2] => baz )


baz
Array ( [0] => foo [1] => bar [2] => baz )

在循环结束时,$item仍然指向与 $arr[2]相同的位置。

当第二个循环运行完成时,我们看到如下输出:

foo
Array ( [0] => foo [1] => bar [2] => foo )


bar
Array ( [0] => foo [1] => bar [2] => bar )


bar
Array ( [0] => foo [1] => bar [2] => bar )

您将注意到,每个时间数组如何将一个新值放入 $item,它还用相同的值更新 $arr[3],因为它们仍然指向相同的位置。当循环到达数组的第三个值时,它将包含值 bar,因为它是由该循环的前一次迭代设置的。

是虫子吗?

没有。这是被引用项的行为,而不是 bug。这将类似于运行类似于:

for ($i = 0; $i < count($arr); $i++) { $item = $arr[$i]; }

Foreach 循环在本质上并不特殊,它可以忽略引用的项。它只是简单地将变量设置为新值,就像您在循环之外所做的那样。

$item是对 $arr[2]的引用,正如 animuson 指出的那样,它被第二个 foreach 循环所覆盖。

foreach ($arr as &$item) { /* do nothing by reference */ }
print_r($arr);


unset($item); // This will fix the issue.


foreach ($arr as $item) { /* do nothing by value */ }
print_r($arr); // $arr has changed....why?

虽然这可能不是一个正式的错误,但在我看来它是。我认为这里的问题是,当循环退出时,我们期望 $item超出范围,就像在其他许多编程语言中一样。然而事实似乎并非如此。

这个密码..。

$arr = array('one', 'two', 'three');
foreach($arr as $item){
echo "$item\n";
}
echo $item;

给出输出..。

one
two
three
three

正如其他人已经说过的,您正在用第二个循环覆盖 $arr[2]中的引用变量,但这只是因为 $item从未超出作用域。你们觉得怎么样... 臭虫?

一个更简单的解释,似乎来自于 PHP 的原创者 Rasmus Lerdorf: https://bugs.php.net/bug.php?id=71454

PHP 的正确行为在我看来应该是一个通知错误。 如果在 foreach 循环中创建的引用变量在循环外使用,则应该引起通知。 这种行为很容易上当,发生时很难发现。 而且没有开发人员会去阅读 foreach 文档页面,这不是一个帮助。

您应该在循环后引用 unset(),以避免此类问题。 Unset ()对引用只会删除引用而不会损害原始数据。

这是因为您使用了 by ref 指令(&)。最后一个值将被第二个循环替换,这将损坏数组。 最简单的解决方案是对第二个循环使用不同的名称:

foreach ($arr as &$item) { ... }


foreach ($arr as $anotherItem) { ... }