谨慎使用PHP的引用原因分析


Posted in PHP onSeptember 06, 2012

引用类型(Reference)在许多计算机语言中都被使用,而且是作为一个非常强大而实用的特性存在。它有类似指针(Pointer)的实现,却又有不同于指针的表现。例如C++的引用,可以让不同变量指向同一个对象,同时又保有直接使用dot来获取对象成员,不用繁琐的使用dereference运算符(*)和Pointer to Member运算符(->)。Java和C#中就直接以引用为主要类型,尽量让开发人员避免使用指针。

PHP中也引入了引用类型,在对对象赋值传递上,基本可视为是同于Java/C#的引用传递(具体请见Objects and references)。但同时又支持在基础类型上通过引用运算符(&)来获得内容的引用。不过在实际的使用中,PHP的引用类型因为整个PHP设计结构而存在着许多的问题,使得在程序出现非预计的结果。

引用变量可被赋予新的引用

在C++中,引用类型的变量只能在其定义时被赋予引用值,所以我们只要追踪到变量的定义处就可以知道变量是在操作哪个内容。

但是PHP不同,PHP里模糊了变量的定义,可以不定义就使用的变量。所以可以让变量被多次赋予引用值。

$x = 21; 
$y = 7; $z = &$x; 
$z = &$y; 
var_dump($x,$y,$z);

初次看起来,让人的感觉是$z变成了对$x的引用,然后让$z的内容变成了对$y的引用,也就是说$x和$z都成对$y的引用。但是实际输出结果是:
int(21) 
int(7) 
int(7)

从结果上看出,$x保持不变,只是$z被改变成了对$y的引用。相当于先unset了$z变量然后赋予了新值。
$z = &$x; 
unset($z); 
$z = &$y;

这其实是比较合理逻辑,就比如下边的代码,我们并不是得到类似于“指向指针的指针(Pointer point to a Pointer)”那样的“引用引用的引用(Reference refer to a Referenece)”,只是多个引用到同一块内容的引用变量。
$x = 21; 
$y = &$x; 
$z = &$y

引用数组元素会让该元素变成引用类型

对于变量上取引用,并不会造成原变量类型的改变,但是如果取的是数组中的元素,却会让该元素也变成引用类型。

在看问题代码前,首先要指出的是:
Array assignment always involves value copying. Use the reference operator to copy an array by reference.

也就是说PHP的数组赋值是copy而非引用,赋值过程会创建新的数组赋予被赋值的变量。在新变量上的数组操作并不会影响到原数组变量中的内容。

$a = array(21, 7); 
$b = $a; 
$b[0] = 7; 
var_dump($a); 
echo '<br/>'; 
var_dump($b); //Output: 
//array(2) { [0]=> int(21) [1]=> int(7) } 
//array(2) { [0]=> int(7) [1]=> int(7) }

下边我们再来看看如果引用数组中的元素,会有什么异常。
$a = array(21, 7); 
$c = & $a[0]; 
$b = $a; 
$b[0]= "21"; 
$b[1]= "7"; var_dump($a); 
echo '<br/>'; 
var_dump($b); 
echo '<br/>'; 
var_dump($c); 
echo '<br/>'; 
// Output: 
// array(2) { [0]=> &string(2) "21" [1]=> int(7) } 
// array(2) { [0]=> &string(2) "21" [1]=> string(1) "7" } 
// string(2) "21"

代码中$b跟之前的只是简单的赋值,只是在之前多了一部取第一个元素的引用,但理应还是拷贝了一个新的数组。可是结果却是对$b的修改,同时也改变了$a的第一个元素,而第二个元素没有影响。

从输出中我们还看到了一个不寻常的地方,就是数组第一个元素的类型多一个‘&'符号。而这个正是取引用运算符。也就是说数组的第一个元素已经变成了引用类型。所以赋值时也是引用拷贝,而非值拷贝。

这个问题十分奇怪,在开发中也造成了许多不必要的困扰,原本以为拷贝出来的数组并没有跟原数组有关联,但是就因为这意外出现的引用类型,让我在操作时也影响到了原数组。

我也不清楚这算是PHP中的bug,还是有意如此设计。在网上找了很久也没有对该方便的相关解释,只有Float Middle的《PHP: References To Array Elements Are Risky》和 Symmetric Designs的《Problems w/accessing a PHP array by reference》里有谈到这个,但是也没有讲原因。

之后又在PHP的Bug Report中看到几篇有联系的报告(Bug6417, Bug7412, Bug15025, Bug20993)。有些说这是个Bug,而且已经在后边的版本被修复。具体我也没有明白,只能避免在数组上使用引用。

更有趣的事情是,如果unset那些引用,只留下一个,那么数组元素又会变成不含有引用的正常类型。

unset($b); 
unset($c); 
var_dump($a); // Output: 
//array(2) { [0]=> string(2) "21" [1]=> int(7) }

避免使用PHP的引用

这个其实这是PHP Array Manual里面提到的要注意的地方,最常发生在foreach的之中,希望通过引用来改变远数组的值(可参见该篇文章)。

其实想通过使用foreach配合引用来改变数组元素的值,主要是因为PHP的数组是Associative Array,这种数组“不定长度,索引可以不连续,可同时用字符串和整数当索引”,所以我们无法用for循环简单增加整数索引。

当然我们可以像下边的代码那样通过$key直接对数组元素改变值,但是这可能存在一定的效率问题。

foreach ($array_var as $key => $value) 
$array_var [$key] = $newValue;

另一个常用的引用的地方是在函数调用中使用引用传递参数。其主要原因是希望通过这种方法让函数实现返回多个返回值。比如我们希望用一个表示指示函数是否在执行中出现error而导致返回值是无效的。

但是因为PHP的函数是可以返回不同的类型的,所以并不需要传入引用参数来作为表示。即使真的需要多个返回值,也可以通过返回“以字符串为主键的数组”作为解决方案,只不过可能需要在文档中指出每个元素都是对应那个结果。

有一个比较好操作方式,应该是每当引用变量不再需要使用时,就即时对该变量使用unset让它切换与内容之间的联系。而且即使该变量不是引用类型,我们确认它不再被使用,对它调用unset也不会有什么问题。至少保证在之后对该变量重新赋值时,并不会影响到之前的结果。

  1. Problems w/accessing a PHP array by reference - Symmetric Designs
  2. PHP: References To Array Elements Are Risky ? Float Middle
  3. References and foreach - Johannes Schlüter
  4. References Explained - PHP Manual
PHP 相关文章推荐
PHP开发的一些注意点总结
Oct 12 PHP
php+jquery编码方面的一些心得(utf-8 gb2312)
Oct 12 PHP
php cookie的操作实现代码(登录)
Dec 29 PHP
PHP中PDO的错误处理
Sep 04 PHP
php检测网页是否被百度收录的函数代码
Oct 09 PHP
php类的定义与继承用法实例
Jul 07 PHP
通过Email发送PHP错误的方法
Jul 20 PHP
php封装好的人民币数值转中文大写类
Dec 20 PHP
PHP实现文件上传下载实例
Oct 18 PHP
PHP unlink与rmdir删除目录及目录下所有文件实例代码
Feb 07 PHP
laravel框架中视图的基本使用方法分析
Nov 23 PHP
PHP使用PDO实现mysql防注入功能详解
Dec 20 PHP
很让人受教的 提高php代码质量36计
Sep 05 #PHP
php控制linux服务器常用功能 关机 重启 开新站点等
Sep 05 #PHP
三个类概括PHP的五种设计模式
Sep 05 #PHP
用来解析.htpasswd文件的PHP类
Sep 05 #PHP
用来解析.htgroup文件的PHP类
Sep 05 #PHP
PHP curl 并发最佳实践代码分享
Sep 05 #PHP
PHP输出数组中重名的元素的几种处理方法
Sep 05 #PHP
You might like
浅析PHP Socket技术
2013/08/02 PHP
详解PHP执行定时任务的实现思路
2015/12/21 PHP
微信公众号开发之文本消息自动回复php代码
2016/08/08 PHP
php、mysql查询当天,查询本周,查询本月的数据实例(字段是时间戳)
2017/02/04 PHP
PHP判断密码强度的方法详解
2017/05/26 PHP
JavaScript DOM 学习第七章 表单的扩展
2010/02/19 Javascript
jquery实现图片等比例缩放以及max-width在ie中不兼容解决
2013/03/21 Javascript
javascript使用onclick事件改变选中行的颜色
2013/12/30 Javascript
jquery取消选择select下拉框示例代码
2014/02/22 Javascript
jQuery实现Twitter的自动文字补齐特效
2014/11/28 Javascript
jQuery实现的超酷苹果风格图标滑出菜单效果代码
2015/09/16 Javascript
JS中的forEach、$.each、map方法推荐
2016/04/05 Javascript
JavaScript实现两个select下拉框选项左移右移
2017/03/09 Javascript
微信小程序动态显示项目倒计时效果
2017/06/13 Javascript
vue 请求后台数据的实例代码
2017/06/22 Javascript
Angularjs上传文件组件flowjs功能
2017/08/07 Javascript
详谈js的变量提升以及使用方法
2018/10/06 Javascript
Vue CLI3.0中使用jQuery和Bootstrap的方法
2019/02/28 jQuery
Node.js Event Loop各阶段讲解
2019/03/08 Javascript
vue点击按钮动态创建与删除组件功能
2019/12/29 Javascript
详细解读tornado协程(coroutine)原理
2018/01/15 Python
python3获取两个日期之间所有日期,以及比较大小的实例
2018/04/08 Python
python使用matplotlib模块绘制多条折线图、散点图
2020/04/26 Python
基于Tensorflow高阶读写教程
2020/02/10 Python
基于python图书馆管理系统设计实例详解
2020/08/05 Python
python中turtle库的简单使用教程
2020/11/11 Python
巴西图书和电子产品购物网站:Saraiva
2017/06/07 全球购物
瑞士灯具购物网站:Lampenwelt.ch
2018/07/08 全球购物
《第一次抱母亲》教学反思
2014/04/16 职场文书
纪念九一八事变83周年国旗下讲话稿
2014/09/15 职场文书
环保守法证明
2015/06/24 职场文书
参观监狱警示教育心得体会
2016/01/15 职场文书
告诉你一个秘密:富人致富的五大优点
2019/07/11 职场文书
导游词之澳门玫瑰圣母堂
2019/12/03 职场文书
SQLServer权限之只开启创建表权限
2022/04/12 SQL Server
Oracle中DBLink的详细介绍
2022/04/29 Oracle