PHP的foreach中使用引用时需要注意的一个问题和解决方法


Posted in PHP onMay 29, 2014

一、问题
先看一个例子:

<?php
$ar = array(1, 2, 3);
var_dump($ar);
foreach ($ar as &$v) {}
foreach ($ar as $v) {}
var_dump($ar);
?>
输出为:

array(3) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
}
array(3) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  &int(2)
}
???为什么没有进行赋值操作,数组最后一个元素的值却发生了改变呢?

我早就发现了这个问题,一开始以为是 PHP 的 bug,就扔着没管它, foreach 中不使用引用就没事, 用 foreach $k => $v 然后 $ar[$k] 来改变原始数组, 略微损失点效率。

二、分析

今天花了点时间,看了 参考 中的文章, 算是稍微明白一点了,原来是这个样子的:

在执行第一个使用引用的 foreach 时, 一开始, $v 指向 $ar[0] 的存储空间,空间内存储着 1 , foreach 结束时, $v 指向 $ar[2] 的存储空间,空间内存储着 3 。 下面要开始执行第二个 foreach 了,注意和第一个 foreach 不同, 第二个 foreach 没有使用引用,那么就是赋值方式, 即将 $ar 的值依次 赋值 给 $v 。 进行到第一个元素时,要将 $ar[0] 赋值给 $v 。 问题就在这里,由于刚刚执行完第一个 foreach, $v 不是一个新变量,而是已经存在的、指向 $ar[2] 的那个 引用 , 如此一来,对 $v 进行赋值的时候,就将 $ar[0] = 1 写入了 $ar[2] 的实际存储空间, 相当于对 $ar[2] 进行赋值。 依此类推,第二个 foreach 执行的结果, 就是数组的最后一个元素变成了倒数第二个元素的值。 参考文章 2 中有详细的示意图。

如果说这是一个错误,那么错误的原因就在于对引用变量的使用。 当引用变量指向和其他变量时,改变引用变量的值当然会影响到他指向的其他变量。 单独说谁都明白,但在这个 foreach 例子中,凑巧了, 同一个变量两次被使用,前一次是引用的身份,后一次是普通变量身份, 就产生了意料之外的效果。 PHP 的开发者也认为,这种情况属于语言特性造成的,不是 bug。 的确,如果要修复这个问题,一种方法是对 foreach 进行特殊处理之外, 另外一种就是限制 foreach 中 $v 的作用域, 这两种方式都与目前 PHP 的语言特性不符,开发人员不愿改, 但还是在 官方文档 中用 Warning 进行了说明。

三、解决方法

简单,但谈不上完美,就是在使用了引用的 foreach 之后, unset 掉 $v , 开始的例子改为:

<?php
$ar = array(1, 2, 3);
var_dump($ar);
foreach ($ar as &$v) {}
unset($v);
foreach ($ar as $v) {}
var_dump($ar);
?>
运行结果:

array(3) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
}
array(3) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
}

参考

Bug #29992 foreach by reference corrupts the array:https://bugs.php.net/bug.php?id=29992
References and foreach:http://schlueters.de/blog/archives/141-References-and-foreach.html

PHP 相关文章推荐
PHP中cookies使用指南
Mar 16 PHP
PHP+JS+rsa数据加密传输实现代码
Mar 23 PHP
php数据库配置文件一般做法分享
Jul 07 PHP
php的hash算法介绍
Feb 13 PHP
ThinkPHP CURD方法之limit方法详解
Jun 18 PHP
PHP中Session和Cookie是如何操作的
Oct 10 PHP
基于ThinkPHP实现批量删除
Dec 18 PHP
PHP函数checkdnsrr用法详解(Windows平台用法)
Mar 21 PHP
ubutu 16.04环境下,PHP与mysql数据库,网页登录验证实例讲解
Jul 20 PHP
PHP针对伪静态的注入总结【附asp与Python相关代码】
Aug 01 PHP
PHP微信企业号开发之回调模式开启与用法示例
Nov 25 PHP
PHP 进程池与轮询调度算法实现多任务的示例代码
Nov 26 PHP
神盾加密解密教程(一)PHP变量可用字符
May 28 #PHP
CI框架开发新浪微博登录接口源码完整版
May 28 #PHP
PHP+javascript制作带提示的验证码源码分享
May 28 #PHP
微信支付开发教程(一)微信支付URL配置
May 28 #PHP
php中$美元符号与Zen Coding冲突问题解决方法分享
May 28 #PHP
php轻松实现中英文混排字符串截取
May 28 #PHP
分享一段php获取linux服务器状态的代码
May 27 #PHP
You might like
PHP 内存缓存加速功能memcached安装与用法
2009/09/03 PHP
iframe自适应宽度、高度 ie6 7 8,firefox 3.86下测试通过
2010/07/29 Javascript
jQuery()方法的第二个参数详解
2015/04/29 Javascript
深入浅析JavaScript中的Function类型
2016/07/09 Javascript
浅谈Node.js:Buffer模块
2016/12/05 Javascript
js中小数向上取整数,向下取整数,四舍五入取整数的实现(必看篇)
2017/02/13 Javascript
underscore之Collections_动力节点Java学院整理
2017/07/10 Javascript
详解Layer弹出层样式
2017/08/21 Javascript
详解webpack4多入口、多页面项目构建案例
2018/05/25 Javascript
vue devtools的安装与使用教程
2018/08/08 Javascript
vue完成项目后,打包成静态文件的方法
2018/09/03 Javascript
如何自动化部署项目?折腾服务器之旅~
2019/04/16 Javascript
使用layui实现树形结构的方法
2019/09/20 Javascript
解决vuex改变了state的值,但是页面没有更新的问题
2020/11/12 Javascript
[01:11:21]DOTA2-DPC中国联赛 正赛 VG vs Elephant BO3 第一场 3月6日
2021/03/11 DOTA
python的pdb调试命令的命令整理及实例
2017/07/12 Python
python PyQt5/Pyside2 按钮右击菜单实例代码
2019/08/17 Python
Django REST框架创建一个简单的Api实例讲解
2019/11/05 Python
python中的线程threading.Thread()使用详解
2019/12/17 Python
Matplotlib绘制雷达图和三维图的示例代码
2020/01/07 Python
Django重设Admin密码过程解析
2020/02/10 Python
Python3+selenium实现cookie免密登录的示例代码
2020/03/18 Python
快速解决jupyter启动卡死的问题
2020/04/10 Python
keras导入weights方式
2020/06/12 Python
详解用python -m http.server搭一个简易的本地局域网
2020/09/24 Python
python中lower函数实现方法及用法讲解
2020/12/23 Python
使用CSS3制作一个简单的进度条(demo)
2017/05/23 HTML / CSS
Java的for语句中break, continue和return的区别
2013/12/19 面试题
优秀团支部事迹材料
2014/02/08 职场文书
人事部岗位职责范本
2014/03/05 职场文书
工程管理英文求职信
2014/03/18 职场文书
软件项目实施计划书
2014/05/02 职场文书
教师培训学习心得体会
2016/01/21 职场文书
2019各种承诺书范文
2019/06/24 职场文书
请学会珍惜眼前,因为人生没有下辈子!
2019/11/12 职场文书
MySQL数据库优化之通过索引解决SQL性能问题
2022/04/10 MySQL