谨慎使用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
PHP 操作文件的一些FAQ总结
Feb 12 PHP
php 无限级分类学习参考之对ecshop无限级分类的解析 带详细注释
Mar 23 PHP
PHP开发需要注意的安全问题
Sep 01 PHP
PHP中空字符串介绍0、null、empty和false之间的关系
Sep 25 PHP
smarty模板中使用get、post、request、cookies、session变量的方法
Apr 24 PHP
php使用google地图应用实例
Dec 31 PHP
thinkPHP删除前弹出确认框的简单实现方法
May 16 PHP
php原生导出excel文件的两种方法(推荐)
Nov 19 PHP
PHP的反射机制实例详解
Mar 29 PHP
PHP实现的CURL非阻塞调用类
Jul 26 PHP
PHP实现通过文本文件统计页面访问量功能示例
Feb 13 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
ThinkPHP入库出现两次反斜线转义及数据库类转义的解决方法
2014/11/04 PHP
使用YII2框架实现微信公众号中表单提交功能
2017/09/04 PHP
JavaScript库 开发规则
2009/01/31 Javascript
让你的CSS像Jquery一样做筛选的实现方法
2011/07/10 Javascript
异步javascript的原理和实现技巧介绍
2012/11/08 Javascript
在jQuery中 关于json空对象筛选替换
2013/04/15 Javascript
JQuery中form验证出错信息的查看方法
2013/10/08 Javascript
jQuery中appendTo()方法用法实例
2015/01/08 Javascript
BootStrap中
2016/12/10 Javascript
Angular工具方法学习
2016/12/26 Javascript
解决AngualrJS页面刷新导致异常显示问题
2017/04/20 Javascript
基于nodejs实现微信支付功能
2017/12/20 NodeJs
vue-cli 使用vue-bus来全局控制的实例讲解
2018/09/15 Javascript
对vue中v-if的常见使用方法详解
2018/09/28 Javascript
基于Vue2实现简易的省市区县三级联动组件效果
2018/11/05 Javascript
使用Angular Cli如何创建Angular私有库详解
2019/01/30 Javascript
Vue组件通信的几种实现方法
2019/04/25 Javascript
详解Vue 换肤方案验证
2019/08/28 Javascript
js用正则表达式筛选年月日的实例方法
2021/01/04 Javascript
Python入门篇之对象类型
2014/10/17 Python
Python3爬虫爬取百姓网列表并保存为json功能示例【基于request、lxml和json模块】
2018/12/05 Python
浅析Python 实现一个自动化翻译和替换的工具
2019/04/14 Python
Python多版本开发环境管理工具介绍
2019/07/03 Python
Django框架静态文件使用/中间件/禁用ip功能实例详解
2019/07/22 Python
TFRecord格式存储数据与队列读取实例
2020/01/21 Python
Python3+selenium实现cookie免密登录的示例代码
2020/03/18 Python
Python爬虫UA伪装爬取的实例讲解
2021/02/19 Python
html5的新玩法——语音搜索
2013/01/03 HTML / CSS
怀念母亲教学反思
2014/04/28 职场文书
财务科长个人对照检查材料
2014/09/18 职场文书
暑期社会实践证明书
2014/11/17 职场文书
安装工程师岗位职责
2015/02/13 职场文书
交通安全温馨提示语
2015/07/14 职场文书
初中体育课教学反思
2016/02/16 职场文书
Lakehouse数据湖并发控制陷阱分析
2022/03/31 Oracle
Golang连接并操作MySQL
2022/04/14 MySQL