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 模板高级篇总结
Dec 21 PHP
使用Xdebug调试和优化PHP程序之[1]
Apr 17 PHP
php输入流php://input使用示例(php发送图片流到服务器)
Dec 25 PHP
php smarty truncate UTF8乱码问题解决办法
Jun 13 PHP
php常用的url处理函数总结
Nov 19 PHP
php强制更新图片缓存的方法
Feb 11 PHP
php防止CC攻击代码 php防止网页频繁刷新
Dec 21 PHP
phpstorm 正则匹配删除空行、注释行(替换注释行为空行)
Jan 21 PHP
Docker搭建自己的PHP开发环境
Feb 24 PHP
PHP Include文件实例讲解
Feb 15 PHP
php项目中类的自动加载实例讲解
Sep 12 PHP
php 中self,this的区别和操作方法实例分析
Nov 04 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/03/04 日漫
[原创]效率较高的php下读取文本文件的代码
2008/07/02 PHP
php socket方式提交的post详解
2008/07/19 PHP
php与XML、XSLT、Mysql的结合运用实现代码
2009/11/19 PHP
PHP邮件发送类PHPMailer用法实例详解
2014/09/22 PHP
PHP简单获取视频预览图的方法
2015/03/12 PHP
PHP经典实用正则表达式小结
2017/05/04 PHP
PHP基于ORM方式操作MySQL数据库实例
2017/06/21 PHP
Alliance vs Liquid BO3 第一场2.13
2021/03/10 DOTA
通过jQuery打造支持汉字,拼音,英文快速定位查询的超级select插件
2010/06/18 Javascript
JS编程小常识很有用
2012/11/26 Javascript
js获取内联样式的方法
2015/01/27 Javascript
Javascript打印局部页面实例
2016/06/21 Javascript
Bootstrap实现导航栏的2种方式
2016/11/28 Javascript
vue.js 实现输入框动态添加功能
2018/06/25 Javascript
使用js实现将后台传入的json数据放在前台显示
2018/08/06 Javascript
解决bootstrap模态框数据缓存的问题方法
2018/08/10 Javascript
VUE中使用MUI方法
2019/02/12 Javascript
微信小程序自定义tabbar custom-tab-bar 6s出不来解决方案(cover-view不兼容)
2019/11/01 Javascript
基于node+websocket+html实现腾讯课堂聊天室聊天功能
2020/03/04 Javascript
[45:32]Liquid vs LGD 2018国际邀请赛淘汰赛BO3 第二场 8.23
2018/08/24 DOTA
Python合并字符串的3种方法
2015/05/21 Python
Flask的图形化管理界面搭建框架Flask-Admin的使用教程
2016/06/13 Python
详解python多线程、锁、event事件机制的简单使用
2018/04/27 Python
Python安装lz4-0.10.1遇到的坑
2018/05/20 Python
python实现傅里叶级数展开的实现
2018/07/21 Python
谈谈Python:为什么类中的私有属性可以在外部赋值并访问
2020/03/05 Python
HTML5 script元素async、defer异步加载使用介绍
2013/08/23 HTML / CSS
Smallable意大利家庭概念店:设计师童装及家居装饰
2018/01/08 全球购物
英国花园、DIY、电器和家居用品商店:Robert Dyas
2019/03/18 全球购物
DIY蛋糕店的创业计划书范文
2013/12/26 职场文书
红旗团支部事迹材料
2014/01/27 职场文书
烈士陵园扫墓感想
2015/08/07 职场文书
《观潮》教学反思
2016/02/17 职场文书
使用Navicat Premium工具将oracle数据库迁移到MySQL
2021/05/27 Oracle
python调用ffmpeg命令行工具便捷操作视频示例实现过程
2021/11/01 Python