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 相关文章推荐
第十节 抽象方法和抽象类 [10]
Oct 09 PHP
一个php作的文本留言本的例子(六)
Oct 09 PHP
php操作JSON格式数据的实现代码
Dec 24 PHP
php短网址和数字之间相互转换的方法
Mar 13 PHP
php算法实例分享
Jul 14 PHP
PHP采用超长(超大)数字运算防止数字以科学计数法显示的方法
Apr 01 PHP
关于Laravel Route重定向的一个注意点
Jan 16 PHP
PHP读取CSV大文件导入数据库的实例
Jul 24 PHP
laravel model模型处理之修改查询或修改字段时的类型格式案例
Oct 17 PHP
Thinkphp框架使用list_to_tree 实现无限级分类列出所有节点示例
Apr 04 PHP
php开发最强大的IDE编辑的phpstorm 2020.2配置Xdebug调试的详细教程
Aug 17 PHP
PHP dirname(__FILE__)原理及用法解析
Oct 28 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
Laravel 5框架学习之Eloquent (laravel 的ORM)
2015/04/08 PHP
PHP连接MSSQL方法汇总
2016/02/05 PHP
PHP使用SOAP扩展实现WebService的方法
2016/04/01 PHP
yii2.0整合阿里云oss上传单个文件的示例
2017/09/19 PHP
PHPStudy下如何为Apache安装SSL证书的方法步骤
2019/01/23 PHP
Yii使用DbTarget实现日志功能的示例代码
2020/07/21 PHP
javascript 图片上传预览-兼容标准
2009/06/01 Javascript
jQuery实战之品牌展示列表效果
2011/04/10 Javascript
解析jquery获取父窗口的元素
2013/06/26 Javascript
js switch case default 的用法示例介绍
2013/10/23 Javascript
Ajax同步与异步传输的示例代码
2013/11/21 Javascript
js图片轮播手动切换效果
2015/11/10 Javascript
快速解决js动态改变dom元素属性后页面及时渲染的问题
2016/07/06 Javascript
Bootstrap Table使用方法详解
2016/08/01 Javascript
详解打造 Vue.js 可复用组件
2017/03/24 Javascript
seajs模块压缩问题与解决方法实例分析
2017/10/10 Javascript
基于JavaScript实现简单的音频播放功能
2018/01/07 Javascript
浅谈vue限制文本框输入数字的正确姿势
2019/09/02 Javascript
Node.js控制台彩色输出的方法与原理实例详解
2019/12/01 Javascript
Selenium执行Javascript脚本参数及返回值过程详解
2020/04/01 Javascript
node.js通过Sequelize 连接MySQL的方法
2020/12/28 Javascript
[01:19:46]EG vs Secret 2019国际邀请赛淘汰赛 胜者组 BO3 第二场 8.21.mp4
2020/07/19 DOTA
python正常时间和unix时间戳相互转换的方法
2015/04/23 Python
Python中import机制详解
2017/11/14 Python
Django 浅谈根据配置生成SQL语句的问题
2018/05/29 Python
python 默认参数相关知识详解
2019/09/18 Python
python 中的命名空间,你真的了解吗?
2020/08/19 Python
HTML5 Canvas渐进填充与透明实现图像的Mask效果
2013/07/11 HTML / CSS
5个HTML5的常用本地存储方式详解与介绍
2021/03/27 HTML / CSS
花卉与景观设计系大学生求职信
2013/10/01 职场文书
就业协议书怎么填
2014/09/15 职场文书
党员公开承诺书2016
2016/03/24 职场文书
JS实现简单控制视频播放倍速的实例代码
2021/04/18 Javascript
golang http使用踩过的坑与填坑指南
2021/04/27 Golang
适合后台管理系统开发的12个前端框架(小结)
2021/06/29 Javascript
opencv检测动态物体的实现
2021/07/21 Python