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下通过curl抓取yahoo boss 搜索结果的实现代码
Jun 10 PHP
利用PHP生成静态HTML文档的原理
Oct 29 PHP
php实现文件下载(支持中文文名)
Dec 04 PHP
一个PHP二维数组排序的函数分享
Jan 17 PHP
PHP获取网页所有连接的方法(附demo源码下载)
Mar 30 PHP
PHP获取用户访问IP地址的5种方法
May 16 PHP
php获取字符串前几位的实例(substr返回字符串的子串用法)
Mar 08 PHP
php判断str字符串是否是xml格式数据的方法示例
Jul 26 PHP
php删除一个路径下的所有文件夹和文件的方法
Feb 07 PHP
PHP使用星号替代用户名手机和邮箱的实现代码
Feb 07 PHP
Yii2框架数据验证操作实例详解
May 02 PHP
解决laravel-admin 自己新建页面里 js 需要刷新一次的问题
Oct 03 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 htmlspecialchars()与shtmlspecialchars()函数的深入分析
2013/06/05 PHP
PHP环境搭建的详细步骤
2016/06/30 PHP
phpstorm 正则匹配删除空行、注释行(替换注释行为空行)
2018/01/21 PHP
Laravel项目中timeAgo字段语言转换的改善方法示例
2019/09/16 PHP
Mootools 1.2教程 同时进行多个形变动画
2009/09/15 Javascript
JS下高效拼装字符串的几种方法比较与测试代码
2010/04/15 Javascript
jqPlot 图表中文API使用文档及源码和在线示例
2012/02/07 Javascript
js取得html iframe中的元素和变量值
2014/06/30 Javascript
基于NodeJS的前后端分离的思考与实践(二)模版探索
2014/09/26 NodeJs
node.js中的http.request方法使用说明
2014/12/14 Javascript
JavaScript+canvas实现七色板效果实例
2016/02/18 Javascript
JavaScript Math 对象常用方法总结
2016/04/28 Javascript
分享JS代码实现鼠标放在输入框上输入框和图片同时更换样式
2016/09/01 Javascript
KnockoutJS 3.X API 第四章之表单value绑定
2016/10/10 Javascript
WEB开发之注册页面验证码倒计时代码的实现
2016/12/15 Javascript
Angular.js初始化之ng-app的自动绑定与手动绑定详解
2017/07/31 Javascript
解决vue中修改export default中脚本报一大堆错的问题
2018/08/27 Javascript
vue-cli安装使用流程步骤详解
2018/11/08 Javascript
详解nvm管理多版本node踩坑
2019/07/26 Javascript
JS实现横向轮播图(中级版)
2020/01/18 Javascript
OpenLayers3实现鼠标移动显示坐标
2020/09/25 Javascript
Python实现的二维码生成小软件
2014/07/11 Python
Python字符串特性及常用字符串方法的简单笔记
2016/01/04 Python
python3 kmp 字符串匹配的方法
2018/07/07 Python
Win10环境python3.7安装dlib模块趟过的坑
2019/08/01 Python
Python实现不规则图形填充的思路
2020/02/02 Python
Python利用逻辑回归分类实现模板
2020/02/15 Python
python wsgiref源码解析
2021/02/06 Python
美国的Eastbay旗下的运动款子品牌:Final-Score
2018/01/01 全球购物
医学院学生的自我评价分享
2013/11/19 职场文书
竞职演讲稿范文
2014/01/11 职场文书
协会周年庆活动方案
2014/08/26 职场文书
干部四风问题整改措施思想汇报
2014/10/13 职场文书
团组织推荐意见
2015/06/05 职场文书
教师信息技术学习心得体会
2016/01/21 职场文书
为Java项目添加Redis缓存的方法
2021/05/18 Redis