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边学边教》(02.Apache+PHP环境配置――下篇)
Dec 13 PHP
php pcntl_fork和pcntl_fork 的用法
Apr 13 PHP
php带密码功能并下载远程文件保存本地指定目录 修改加强版
May 16 PHP
php shell超强免杀、减少体积工具实现代码
Oct 16 PHP
解析php中session的实现原理以及大网站应用应注意的问题
Jun 17 PHP
php函数指定默认值方法的小例子
Dec 04 PHP
10款PHP开源商城系统汇总介绍
Jul 23 PHP
PHP中Http协议post请求参数
Nov 02 PHP
PHP面向对象程序设计之类与反射API详解
Dec 02 PHP
php修改数组键名的方法示例
Apr 15 PHP
php格式文件打开的四种方法
Feb 24 PHP
Laravel5.4框架中视图共享数据的方法详解
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
苏联队长,苏联超人蝙蝠侠,这些登场的“山寨”英雄真的很严肃
2020/04/09 欧美动漫
php实现搜索一维数组元素并删除二维数组对应元素的方法
2015/07/06 PHP
PHP设计模式之工厂模式定义与用法详解
2018/04/03 PHP
Laravel框架Eloquent ORM简介、模型建立及查询数据操作详解
2019/12/04 PHP
jquery 插件开发方法小结
2009/10/23 Javascript
关于eval 与new Function 到底该选哪个?
2013/04/17 Javascript
判断javascript的数据类型(示例代码)
2013/12/11 Javascript
5个JavaScript经典面试题
2014/10/13 Javascript
原生JavaScript编写俄罗斯方块
2015/03/30 Javascript
jQuery的Scrollify插件实现滑动到页面下一节点
2015/07/05 Javascript
JavaScript jquery及AJAX小结
2016/01/24 Javascript
JavaScript判断DIV内容是否为空的方法
2016/01/29 Javascript
Javascript基础_简单比较undefined和null 值
2016/06/14 Javascript
vue中$refs的用法及作用详解
2018/04/24 Javascript
VUE-Table上绑定Input通过render实现双向绑定数据的示例
2018/08/27 Javascript
微信小程序开发搜索功能实现(前端+后端+数据库)
2020/03/04 Javascript
Python3.x和Python2.x的区别介绍
2013/02/12 Python
python对字典进行排序实例
2014/09/25 Python
简单实现python聊天程序
2018/04/01 Python
Python之修改图片像素值的方法
2019/07/03 Python
pygame实现贪吃蛇游戏(下)
2019/10/29 Python
python随机模块random的22种函数(小结)
2020/05/15 Python
利用Python实现斐波那契数列的方法实例
2020/07/26 Python
手摸手教你用canvas实现给图片添加平铺水印的实现
2019/08/20 HTML / CSS
德国电子商城:ComputerUniverse
2017/04/21 全球购物
GWT的应用有哪两种部署模式
2012/12/21 面试题
女娲补天教学反思
2014/02/05 职场文书
成龙霸王洗发水广告词
2014/03/14 职场文书
教师党员自我评议不足范文
2014/10/19 职场文书
2014党的群众路线教育实践活动学习心得体会
2014/10/31 职场文书
经理聘任证明
2015/03/02 职场文书
电影复兴之路观后感
2015/06/02 职场文书
Nginx已编译的nginx-添加新模块
2021/04/01 Servers
python自动化之如何利用allure生成测试报告
2021/05/02 Python
openstack中的rpc远程调用的方法
2021/07/09 Python
15个值得收藏的JavaScript函数
2021/09/15 Javascript