关于PHP的引用在foreach中的一个坑

2,431 阅读1分钟
原文链接: zicai.fun

Passingforreferenceinphp

December 17, 2017 php zicai

PHP的引用在foreach中使用需要注意的事


最近1个月在掘金上经常看到类似这样的问题:

juejin.cn/post/684490… juejin.cn/post/684490…

本篇文章就此问题做一个详细说明,下面是问题的简单重现:

<?php
$arr = [1,2,3];
foreach ($arr as &$item) {
    echo $item."\n";
}

foreach ($arr as $item) {
    echo $item."\n";
}

运行后的显示结果:

1
2
3
1
2
2

看似没有做任何修改$arr变量的动作,但打印出来后结果却是出人意料,在第二次foreach的最后一次输出,其结果居然是2而不是3,为什么呢?

PHP文档中的解释

文档中告诉了大家如何避免这个问题,那就是在foreach结束后unset($item)来取消引用。

如果我们不取消引用呢?

第一次foreach

那么在第一次foreach循环结束后:

<?php
$arr = [1,2,3];
foreach ($arr as &$item) {
    echo $item."\n";
}
echo '$item:'.$item."\n";

我们可以看到其结果为:

1
2
3
$item:3

也就是说此时$item的依然代表着$arr[2]的值,下面来代码证实:

<?php
$arr = [1,2,3];
foreach ($arr as &$item) {
    echo $item."\n";
}
echo '$item:'.$item."\n";
$arr[2] = 10;
echo '$item:'.$item."\n";

运行后结果是:

1
2
3
$item:3
$item:10

在第一次foreach结束并打印一次$item之后我们修改$arr[2]=10,再次打印$item 得到 10,这就印证了我们的想法

第二次foreach

第二次foreach中我们同样使用了$item作为循环当前值的赋值对象,这与我们第一次foreach中的赋值对象$item相同,问题出在第一次foreach中$item是赋值的形式,那么当第一次循环时我们不光将$arr[0]的值赋给了$item并显示出来,还同时用$arr[0]更改了它所代表的$arr[2]的值。同样的,当第二次循环时我们将$arr[1]赋值给了$item并显示出来,还同时用$arr[1]更改了$item引用的$arr[2]。而到了第三次循环,显示的自然就是被改变后的$arr[2]了。

为了看到跟明显一些,我们在第二次foreach中打印$arr[2]的值:

<?php
$arr = [1,2,3];
foreach ($arr as &$item) {
    echo $item."\n";
}

foreach ($arr as $item) {
    echo '当前$arr[2]的值是:'.$arr[2]."\n";
    echo $item."\n";
}

运行的结果是:

1
2
3
当前$arr[2]的值是:1
1
当前$arr[2]的值是:2
2
当前$arr[2]的值是:2
2

解决办法

1.按照PHP官方手册所说,在第一次foreach后我们使用unset($item)来取消引用

2.我们可以在第二次foreach时同样使用引用的方式&$item来重现赋值

总结:

foreach在每一次loop中都将当前值赋给我们定义的$value,如果在foreach后不及时删除$value,那么$value依然可以在foreach后继续使用,所有为了安全起见每次foreach完后都unset()掉定义的$value和$key是一个好的习惯。