简单分析一下 PHP 中`foreach ($data as &$item) `循环引用产生的问题

2020-12-02 18:09:41 +08:00
 JJstyle

最小化分析代码:

$data = ['foo', 'bar'];

foreach ($data as &$item) {
}

foreach ($data as $item) {
}

print_r($data);

输出结果:

Array
(
    [0] => 'foo'
    [1] => 'foo'
)

我们可以发现,$data的值 ~~莫名奇妙~~ 变了,而它只是经过了两个空循环而已,发生了什么?!

下面我来一行行代码分析产生这个问题的原因:

先总结一下 PHP 中两条关于引用的两个规则:

  1. 给引用变量赋值,实际上是给引用所指向的变量赋值
  2. 一个引用变量可以被修改为对另外一个变量的引用

分析开始:

$data = ['foo', 'bar'];

// 循环开始,$item 变量不存在,新建一个$item 变量,且是一个引用变量,它不指向任何变量地址
foreach ($data as &$item) {
    // loop 1: 执行了 $item = &$data[0];$item 指向 $data[0] 的地址
    // loop 2: 执行了 $item = &$data[1];$item 指向 $data[1] 的地址
}
// 提示:这个循环没有改变 $data 的数据,只是 $item 依然指向第二个元素 的地址

// 循环开始,$item 变量存在,不会新建变量
foreach ($data as $item) {
    // loop 1: 执行了 $item = $data[0];$item 所指向的变量(即 第二个元素)的值被修改为$data[0](即'foo'),这里已经导致了$data 两个元素都等于 'foo'
    // loop 2: 执行了 $item = $data[1];由于$item 指向的是$data[1],实际上相当于执行$data[1] = $data[1],没有任何意义
}
// 最后$data 中的两个元素都是 'foo'

如何避免这个问题:

foreach ($data as &$item) {

  // 每次 loop 销毁$item (实际上只要在最后一次 loop 销毁即可,因此你可以把 unset 写到 foreach 后面,就是不是很好看)
  unset($item);
}

~这里没有二维码和其他链接~

2270 次点击
所在节点    PHP
23 条回复
sleepm
2020-12-03 23:27:28 +08:00
Psy Shell v0.9.12 (PHP 7.4.3 — cli)
是 foo foo
所以应该是旧版本的 bug
@JJstyle
ChoateYao
2020-12-07 19:42:59 +08:00
因为 foreach 申明的是一个在该上下文中的全局变量,一般的建议是不要使用相同的变量名。

包括 for 语法也一样。

主要还是 PHP 中 foreach 、for 这类语法的代码块中声明的变量不属于 foreach 、for 的上下文,所以你能在 foreach 、for 之外使用 foreach 、for 中声明的变量。
Varobjs
2020-12-08 11:12:02 +08:00
不是引用到锅,
是习惯,不管后面有没有再用$item 都要 unset,这是好习惯

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/731473

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX