谨慎使用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类
Apr 09 PHP
模板引擎正则表达式调试小技巧
Jul 20 PHP
基于PHP Web开发MVC框架的Smarty使用说明
Apr 19 PHP
使用YUI+Ant 实现JS CSS压缩
Sep 02 PHP
php中使用session_set_save_handler()函数把session保存到MySQL数据库实例
Nov 06 PHP
php实现的SESSION类
Dec 02 PHP
Opcache导致php-fpm崩溃nginx返回502
Mar 02 PHP
php实现Mongodb自定义方式生成自增ID的方法
Mar 23 PHP
php动态函数调用方法
May 21 PHP
PHP静态成员变量和非静态成员变量详解
Feb 14 PHP
PHP API接口必备之输出json格式数据示例代码
Jun 27 PHP
tp5.1 框架数据库高级查询技巧实例总结
May 25 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读取目录下所有文件的代码
2008/01/07 PHP
php print EOF实现方法
2009/05/21 PHP
javascript下有关dom以及xml节点访问兼容问题
2007/11/26 Javascript
javascript 写类方式之四
2009/07/05 Javascript
不同浏览器对回车提交表单的处理办法
2010/02/13 Javascript
禁止js文件缓存的代码
2010/04/09 Javascript
js关闭浏览器窗口及检查浏览器关闭事件
2013/09/03 Javascript
jQuery 顶部导航跟随滚动条滚动固定浮动在顶部
2014/06/06 Javascript
jQuery实现在新增加的元素上添加事件方法案例分析
2017/02/09 Javascript
JS 插件dropload下拉刷新、上拉加载使用小结
2017/04/13 Javascript
Vue分页组件实例代码
2017/04/17 Javascript
浅析 NodeJs 的几种文件路径
2017/06/07 NodeJs
Angular弹出模态框的两种方式
2017/10/19 Javascript
layui自定义工具栏的方法
2019/09/19 Javascript
微信小程序 冒泡事件原理解析
2019/09/27 Javascript
vue使用微信扫一扫功能的实现代码
2020/04/11 Javascript
linux系统使用python监测系统负载脚本分享
2014/01/15 Python
Python实现监控程序执行时间并将其写入日志的方法
2015/06/30 Python
python实现斐波那契数列的方法示例
2017/01/12 Python
Python实现采用进度条实时显示处理进度的方法
2017/12/19 Python
Python爬虫中urllib库的进阶学习
2018/01/05 Python
使用Python实现租车计费系统的两种方法
2018/09/29 Python
Python编程实现tail-n查看日志文件的方法
2019/07/08 Python
python求一个字符串的所有排列的实现方法
2020/02/04 Python
CSS3 Flexbox中flex-shrink属性的用法示例介绍
2013/12/30 HTML / CSS
女士鞋子、包包和服装在线,第一款10美元:ShoeDazzle
2019/07/26 全球购物
银行领导证婚词
2014/01/11 职场文书
创建文明城市标语
2014/06/16 职场文书
教师辞职书范文
2015/02/26 职场文书
2015年秋季小学开学标语
2015/07/16 职场文书
python基础学习之生成器与文件系统知识总结
2021/05/25 Python
深入理解go缓存库freecache的使用
2022/02/15 Golang
解析探秘fescar分布式事务实现原理
2022/02/28 Java/Android
Spring依赖注入多种类型数据的示例代码
2022/03/31 Java/Android
CDPR谈《巫师》新作用虚幻5原因 称不会为Epic独占
2022/04/06 其他游戏
python标准库ElementTree处理xml
2022/05/20 Python