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

时间:2011-11-22 00:21:09

标签: php arrays loops reference

我刚刚写了一个简单的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
)

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

6 个答案:

答案 0 :(得分:168)

在第一个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)。

第二个foreach循环的第一次调用实际上丢失了'baz'值。

调试输出

对于循环的每次迭代,我们将回显$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,因为它只是由该循环的前一次迭代设置。

这是一个错误吗?

没有。这是引用项的行为,而不是错误。它类似于运行类似的东西:

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

foreach循环本质上并不特殊,它可以忽略引用的项目。它只是像每次循环一样将变量设置为新值。

答案 1 :(得分:28)

$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?

答案 2 :(得分:3)

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

此代码......

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

给出输出......

one
two
three
three

正如其他人已经说过的那样,你用第二个循环覆盖$arr[2]中的引用变量,但它只是因为$item从未超出范围而发生。你觉得怎么样?bug?

答案 3 :(得分:0)

更简单的解释,来自PHP的原始创建者Rasmus Lerdorf:https://bugs.php.net/bug.php?id=71454

答案 4 :(得分:0)

PHP的正确行为在我的意见中可能是一个NOTICE错误。 如果在foreach循环中创建的引用变量在循环外部使用,则应该引发通知。 这种行为很容易摔倒,发生时很难发现它。 没有开发人员会阅读foreach文档页面,它不是一个帮助。

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

答案 5 :(得分:0)

那是因为你使用ref指令(&amp;)。最后一个值将被第二个循环替换,它会破坏你的数组。 最简单的解决方案是为第二个循环使用不同的名称:

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

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