谨慎使用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数据库连接类~~做成一个分页类!
Nov 25 PHP
PHP strtr() 函数使用说明
Nov 21 PHP
PHP 日期时间函数的高级应用技巧
Oct 10 PHP
php设计模式 Strategy(策略模式)
Jun 26 PHP
PHP APC的安装与使用详解
Jun 13 PHP
解析strtr函数的效率问题
Jun 26 PHP
php数组查找函数in_array()、array_search()、array_key_exists()使用实例
Apr 29 PHP
PHP curl 或 file_get_contents 获取需要授权页面的方法
May 05 PHP
Windows下wamp php单元测试工具PHPUnit安装及生成日志文件配置方法
May 28 PHP
PHP面向对象程序设计之对象的遍历操作示例
Jun 12 PHP
TP5框架实现一次选择多张图片并预览的方法示例
Apr 04 PHP
详解PHP服务器如何在有限的资源里最大提升并发能力
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脚本中include文件出错解决方法
2008/11/20 PHP
PHP的AES加密算法完整实例
2016/07/20 PHP
PHP CURL与java http使用方法详解
2018/01/26 PHP
PHP面向对象程序设计重载(overloading)操作详解
2019/06/13 PHP
Javascript 获取滚动条位置等信息的函数
2009/09/08 Javascript
jQuery仿Excel表格编辑功能的实现代码
2013/05/01 Javascript
浅析jQuery对select操作小结(遍历option,操作option)
2013/07/04 Javascript
Jquery节点遍历next与nextAll方法使用示例
2014/07/22 Javascript
javascript实现表格排序 编辑 拖拽 缩放
2015/01/02 Javascript
javascript中关于&amp;&amp; 和 || 表达式的小技巧分享
2015/04/10 Javascript
原生js配合cookie制作保存路径的拖拽
2015/12/29 Javascript
简单谈谈Vue 模板各类数据绑定
2016/09/25 Javascript
Avalonjs 实现简单购物车功能(实例代码)
2017/02/07 Javascript
vue 实现的树形菜的实例代码
2018/03/19 Javascript
Bootstrap标签页(Tab)插件切换echarts不显示问题的解决
2018/07/13 Javascript
小程序指纹验证的实现代码
2018/12/04 Javascript
微信小程序:数据存储、传值、取值详解
2019/05/07 Javascript
Webpack按需加载打包chunk命名的方法
2019/09/22 Javascript
node.js +mongdb实现登录功能
2020/06/18 Javascript
Element Badge标记的使用方法
2020/07/27 Javascript
Vue实现简易购物车页面
2020/12/30 Vue.js
pandas按若干个列的组合条件筛选数据的方法
2018/04/11 Python
pyinstaller打包多个py文件和去除cmd黑框的方法
2019/06/21 Python
python学生信息管理系统实现代码
2019/12/17 Python
IDLE下Python文件编辑和运行操作
2020/04/25 Python
CSS3实现图片抽屉式效果的示例代码
2019/11/06 HTML / CSS
HTML5 canvas标签实现刮刮卡效果
2015/04/24 HTML / CSS
英国羊皮鞋类领先品牌:Just Sheepskin
2019/12/12 全球购物
个人自我鉴定
2013/11/07 职场文书
养殖项目策划书范文
2014/01/13 职场文书
好军嫂事迹材料
2014/01/15 职场文书
《中国梦我的梦》小学生演讲稿
2014/08/20 职场文书
旷工检讨书1000字
2015/01/01 职场文书
2016年小学端午节活动总结
2016/04/01 职场文书
python 利用PyAutoGUI快速构建自动化操作脚本
2021/05/31 Python
MySQL悲观锁与乐观锁的实现方案
2021/11/02 MySQL