PHP Foreach 通过引用传递:最后一个元素重复? (错误?)[重复]

Posted

技术标签:

【中文标题】PHP Foreach 通过引用传递:最后一个元素重复? (错误?)[重复]【英文标题】:PHP Foreach Pass by Reference: Last Element Duplicating? (Bug?) [duplicate] 【发布时间】:2012-01-03 11:00:43 【问题描述】:

我正在编写的一个简单的 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
)

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

【问题讨论】:

再按值做,看第三次有没有变化……? @Shackrock,它似乎不再随着按值重复循环而改变。 有趣的是,如果您将第二个循环更改为使用 $item 以外的其他内容,那么它会按预期工作。 总是取消设置循环体末尾的项目:foreach($x AS &amp;$y) ... unset($y); -- 它实际上在 php.net 上(不知道在哪里),因为这是一个很大的错误。 PHP 手册建议在第一个 foreach 循环之后调用 unset($item); 以避免此问题。 php.net/manual/en/control-structures.foreach.php 【参考方案1】:

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

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

foreach ($arr as $anotherItem)  ... 

【讨论】:

【参考方案2】:

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

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

【讨论】:

【参考方案3】:

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

【讨论】:

本站前面给出了类似的解释:***.com/a/19829715 rasmus@php.net 之前在bugs.php.net/bug.php?id=29992 中给出了类似的解释【参考方案4】:

在第一个 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,因为它只是由该循环的上一次迭代设置的。

这是一个错误吗?

没有。这是引用项目的行为,而不是错误。这类似于运行以下内容:

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

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

【讨论】:

我有一点迂腐的修正。 $item 不是对$arr[2] 的引用,$arr[2] 包含的值是对$item 引用的值的引用。为了说明差异,您还可以取消设置$arr[2]$item 将不受影响,写入$item 不会影响它。 这种行为很难理解,可能会导致问题。我把它作为我的最爱之一,向我的学生展示为什么他们应该(只要他们可以)“通过参考”避免事情。 为什么在 foreach 循环退出时$item 不会超出范围?这似乎是一个关闭问题? @jocull:在 PHP 中,foreach、for、while 等不创建自己的范围。 @jocull,PHP 没有(块)局部变量。它让我烦恼的原因之一。【参考方案5】:

虽然这可能不是正式的错误,但在我看来是。我认为这里的问题是我们期望$item 在退出循环时超出范围,就像在许多其他编程语言中一样。不过好像不是这样的……

这段代码...

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

给出输出...

one
two
three
three

正如其他人已经说过的,您正在用第二个循环覆盖$arr[2] 中的引用变量,但这只是因为$item 从未超出范围而发生。你们怎么看...错误?

【讨论】:

1) 不是错误。它已经在manual 中被调用,并在许多错误报告中按预期被驳回。 2) 没有真正回答问题... 这不是因为范围问题而让我感到震惊,我希望 $item 在初始 foreach 之后仍然存在,但我没有意识到 foreach 更新变量而不是替换它。例如,与在第二个循环之前运行 unset($item) 相同。请注意,未设置不会清除值(因此数组中的最后一个元素)它只是删除变量。 不幸的是,PHP 通常不会为循环或 块创建新范围。这就是语言的工作原理【参考方案6】:

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

【讨论】:

以上是关于PHP Foreach 通过引用传递:最后一个元素重复? (错误?)[重复]的主要内容,如果未能解决你的问题,请参考以下文章

php foreach循环中的变量

PHP:“......变量可以通过引用传递”在str_replace()?

php foreach 遍历细节探讨

在 PHP 中使用 foreach 循环时查找数组的最后一个元素

php新手常见问题

php数组指针