谨慎使用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 09 PHP
Banner程序
Oct 09 PHP
PHP反转字符串函数strrev()函数的用法
Feb 04 PHP
PHP生成短网址的3种方法代码实例
Jul 08 PHP
PHP图片库imagemagick安装方法
Sep 23 PHP
基于ThinkPHP实现批量删除
Dec 18 PHP
CI(CodeIgniter)模型用法实例分析
Jan 20 PHP
PHP基于GD库的图像处理方法小结
Sep 27 PHP
PHP查询大量数据内存耗尽问题的解决方法
Oct 28 PHP
php把时间戳转换成多少时间之前函数的实例
Nov 16 PHP
PHP中遍历数组的三种常用方法实例分析
Jun 24 PHP
php实现通过stomp协议连接ActiveMQ操作示例
Feb 23 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
历史证明,懒惰才是推动科学发展技术进步的动力
2021/03/02 无线电
php strcmp使用说明
2010/04/22 PHP
PHP积分兑换接口实例
2015/02/09 PHP
PHP根据session与cookie用户登录状态操作类的代码
2016/05/13 PHP
php中Redis的应用--消息传递
2017/03/28 PHP
PHP实现的字符串匹配算法示例【sunday算法】
2017/12/19 PHP
ThinkPHP5.0框架实现切换数据库的方法分析
2019/10/30 PHP
基于jquery tab切换(防止页面刷新)
2012/05/23 Javascript
jquery获取tagName再进行判断
2014/05/29 Javascript
什么是MEAN?JavaScript编程中的MEAN是什么意思?
2014/12/18 Javascript
Javascript常用小技巧汇总
2015/06/24 Javascript
基于js粘贴事件paste简单解析以及遇到的坑
2017/09/07 Javascript
js实现控制文件拖拽并获取拖拽内容功能
2018/02/17 Javascript
layer.open 按钮的点击事件关闭方法
2018/08/17 Javascript
javascript简单实现深浅拷贝过程详解
2019/10/08 Javascript
javascript数组的定义及操作实例
2019/11/10 Javascript
小程序实现列表展开收起效果
2020/07/29 Javascript
caffe binaryproto 与 npy相互转换的实例讲解
2018/07/09 Python
详解python selenium 爬取网易云音乐歌单名
2019/03/28 Python
python调用动态链接库的基本过程详解
2019/06/19 Python
Python从入门到精通之环境搭建教程图解
2019/09/26 Python
python 并发下载器实现方法示例
2019/11/22 Python
Python异步编程之协程任务的调度操作实例分析
2020/02/01 Python
Python matplotlib可视化实例解析
2020/06/01 Python
python中re模块知识点总结
2021/01/17 Python
canvas实现图片马赛克的示例代码
2018/03/26 HTML / CSS
英国领先的NHS批准的在线药店:Pharmacy2U
2017/01/06 全球购物
档案管理员岗位职责
2013/12/01 职场文书
迎新晚会邀请函
2014/02/01 职场文书
优秀老员工获奖感言
2014/02/15 职场文书
大学生个人自荐信
2014/02/24 职场文书
学生会主席演讲稿
2014/04/25 职场文书
实践单位评语
2014/04/26 职场文书
个人作风纪律整顿整改措施
2014/10/25 职场文书
党校培训学习心得体会
2016/01/06 职场文书
给原生html中添加水印遮罩层的实现示例
2021/04/02 Javascript