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 相关文章推荐
一个取得文件扩展名的函数
Oct 09 PHP
解析htaccess伪静态的规则
Jun 18 PHP
浅析echo(),print(),print_r(),return之间的区别
Nov 27 PHP
php导入excel文件到mysql数据库的方法
Jan 14 PHP
php curl 上传文件代码实例
Apr 27 PHP
CodeIgniter中使用Smarty3基本配置
Jun 29 PHP
非常有用的9个PHP代码片段
Apr 06 PHP
PHP发送AT指令实例代码
May 26 PHP
PHP排序算法之简单选择排序(Simple Selection Sort)实例分析
Apr 20 PHP
Laravel框架实现的rbac权限管理操作示例
Jan 16 PHP
Laravel5.7 数据库操作迁移的实现方法
Apr 12 PHP
php生成微信红包数组的方法
Sep 05 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
基于Zend的Captcha机制的应用
2013/05/02 PHP
Symfony2创建页面实例详解
2016/03/18 PHP
PHP延迟静态绑定的深入讲解
2018/04/02 PHP
php实现微信和支付宝支付的示例代码
2020/08/11 PHP
用cookies实现的可记忆的样式切换效果代码下载
2007/12/24 Javascript
Javascript 面向对象 重载
2010/05/13 Javascript
javascript学习笔记(三) String 字符串类型介绍
2012/06/19 Javascript
修复bash漏洞的shell脚本分享
2014/12/31 Javascript
JavaScript中的Math.SQRT1_2属性使用简介
2015/06/14 Javascript
IE6兼容透明背景图片及解决方案
2015/08/19 Javascript
在JavaScript中使用JSON数据
2016/02/15 Javascript
Bootstrap嵌入jqGrid,使你的table牛逼起来
2016/05/05 Javascript
ES6数组的扩展详解
2017/04/25 Javascript
React Native使用Modal自定义分享界面的示例代码
2017/10/31 Javascript
JS计算距当前时间的时间差实例
2017/12/29 Javascript
微信小程序实现时间预约功能
2018/11/27 Javascript
vue 中几种传值方法(3种)
2019/11/12 Javascript
jQuery使用ajax传递json对象到服务端及contentType的用法示例
2020/03/12 jQuery
js 函数性能比较方法
2020/08/24 Javascript
PyTorch之图像和Tensor填充的实例
2019/08/18 Python
Tensorflow: 从checkpoint文件中读取tensor方式
2020/02/10 Python
django 模型字段设置默认值代码
2020/07/15 Python
HTML5中的Article和Section元素认识及使用
2013/03/22 HTML / CSS
毕业自我评价范文
2013/11/17 职场文书
就业推荐表自我鉴定范文
2014/03/21 职场文书
幼儿园课题实施方案
2014/05/14 职场文书
cf战队收人口号
2014/06/21 职场文书
国际金融专业自荐信
2014/07/05 职场文书
争先创优演讲稿
2014/09/15 职场文书
法人身份证明书
2014/10/08 职场文书
英文升职感谢信
2015/01/23 职场文书
2015小学音乐教师个人工作总结
2015/07/21 职场文书
2016年乡镇综治宣传月活动总结
2016/03/16 职场文书
前端学习——JavaScript原生实现购物车案例
2021/03/31 Javascript
SpringBoot项目中控制台日志的保存配置操作
2021/06/18 Java/Android
Win11怎么跳过联网验机 ?Win11跳过联网验机激活教程
2022/04/05 数码科技