谨慎使用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
桌面中心(四)数据显示
Oct 09 PHP
比较时间段一与时间段二是否有交集的php函数
May 31 PHP
深入理解PHP之数组(遍历顺序)  Laruence原创
Jun 13 PHP
探讨:使用XMLSerialize 序列化与反序列化
Jun 08 PHP
解析php中static,const与define的使用区别
Jun 18 PHP
免费手机号码归属地API查询接口和PHP使用实例分享
Apr 10 PHP
高性能PHP框架Symfony2经典入门教程
Jul 08 PHP
PHP入门教程之表单与验证实例详解
Sep 11 PHP
PHP中串行化用法示例
Nov 16 PHP
php实现保存周期为1天的购物车类
Jul 07 PHP
PHP性能分析工具xhprof的安装使用与注意事项
Dec 19 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
Windows下PHP5和Apache的安装与配置
2006/09/05 PHP
PHP邮件专题
2006/10/09 PHP
PHP中冒号、endif、endwhile、endfor使用介绍
2010/04/28 PHP
PHP排序之二维数组的按照字母排序实现代码
2011/08/13 PHP
php实现的发送带附件邮件类实例
2014/09/22 PHP
THINKPHP内容分页代码分享
2015/01/14 PHP
关于laravel5.5的定时任务详解(demo)
2019/10/23 PHP
YUI 读码日记之 YAHOO.util.Dom - Part.1
2008/03/22 Javascript
定义select的边框颜色
2008/04/28 Javascript
jQuery源码分析-03构造jQuery对象-工具函数
2011/11/14 Javascript
JavaScript 高级篇之DOM文档,简单封装及调用、动态添加、删除样式(六)
2012/04/07 Javascript
javascript确认框的三种使用方法
2013/12/17 Javascript
JavaScript网页定位详解
2014/01/13 Javascript
Egret引擎开发指南之视觉编程
2014/09/03 Javascript
js父页面与子页面不同时显示的方法
2014/10/16 Javascript
JavaScript页面模板库handlebars的简单用法
2015/03/02 Javascript
javascript实现验证身份证号的有效性并提示
2015/04/30 Javascript
基于node.js依赖express解析post请求四种数据格式
2017/02/13 Javascript
NodeJs的fs读写删除移动监听
2017/04/28 NodeJs
理解nodejs的stream和pipe机制的原理和实现
2017/08/12 NodeJs
使用form-create动态生成vue自定义组件和嵌套表单组件
2019/01/18 Javascript
vue动态禁用控件绑定disable的例子
2019/10/28 Javascript
vue实现数字动态翻牌的效果(开箱即用)
2019/12/08 Javascript
微信小程序实现二维码签到考勤系统
2020/01/16 Javascript
JS实现横向轮播图(初级版)
2020/06/24 Javascript
JS实现超级好看的鼠标小尾巴特效
2020/12/01 Javascript
[01:00:44]DOTA2上海特级锦标赛主赛事日 - 3 败者组第三轮#1COL VS Alliance第三局
2016/03/04 DOTA
[45:50]完美世界DOTA2联赛PWL S3 CPG vs Forest 第二场 12.16
2020/12/17 DOTA
Python学习之Django的管理界面代码示例
2018/02/10 Python
keras topN显示,自编写代码案例
2020/07/03 Python
HTML5 语音搜索只需一句代码
2013/01/03 HTML / CSS
印度和世界各地的精美产品:Ikka Dukka
2018/02/12 全球购物
大学生的四年学习自我评价
2013/12/13 职场文书
大学生个人求职信
2014/06/02 职场文书
《花钟》教学反思
2016/02/17 职场文书
解决python3安装pandas出错的问题
2021/05/20 Python