谨慎使用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 CURL模拟GET及POST函数代码
Apr 25 PHP
php中mysql连接和基本操作代码(快速测试使用,简单方便)
Apr 25 PHP
php小技巧之过滤ascii控制字符
May 14 PHP
解决CodeIgniter伪静态失效
Jun 09 PHP
jQuery Mobile + PHP实现文件上传
Dec 12 PHP
PHP7.1新功能之Nullable Type用法分析
Sep 26 PHP
老生常谈PHP中的数据结构:DS扩展
Jul 17 PHP
php封装单文件上传到数据库(路径)
Oct 15 PHP
CI框架实现创建自定义类库的方法
Dec 25 PHP
Yii2.0建立公共方法简单示例
Jan 29 PHP
基于Laravel-admin 后台的自定义页面用法详解
Sep 30 PHP
PHP使用openssl扩展实现加解密方法示例
Feb 20 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
在普通HTTP上安全地传输密码
2007/07/21 PHP
解析array splice的移除数组中指定键的值,返回一个新的数组
2013/07/02 PHP
Windows和Linux中php代码调试工具Xdebug的安装与配置详解
2014/05/08 PHP
linux下使用crontab实现定时PHP计划任务失败的原因分析
2014/07/05 PHP
PHP连接MYSQL数据库实例代码
2016/01/20 PHP
Yii2框架dropDownList下拉菜单用法实例分析
2016/07/18 PHP
PHP与Perl之间知识点区别整理
2019/03/19 PHP
php 自定义函数实现将数据 以excel 表格形式导出示例
2019/11/13 PHP
JavaScript 10件让人费解的事情
2010/02/15 Javascript
将两个div左右并列显示并实现点击标题切换内容
2013/10/22 Javascript
php的文件上传入门教程(实例讲解)
2014/04/10 Javascript
JavaScript中的console.trace()函数介绍
2014/12/29 Javascript
多种JQuery循环滚动文字图片效果代码
2020/06/23 Javascript
原生JS实现-星级评分系统的简单实例
2016/08/21 Javascript
Javascript中八种遍历方法的执行速度深度对比
2017/04/25 Javascript
iscroll.js滚动加载实例详解
2017/07/18 Javascript
mongoose更新对象的两种方法示例比较
2017/12/19 Javascript
Vue3.0数据响应式原理详解
2019/10/09 Javascript
vue使用swiper.js重叠轮播组建样式
2019/11/14 Javascript
js实现圆形显示鼠标单击位置
2020/02/11 Javascript
js实现简单的点名器随机色实例代码
2020/09/20 Javascript
DJANGO-ALLAUTH社交用户系统的安装配置
2014/11/18 Python
深入学习python的yield和generator
2016/03/10 Python
浅谈Python中用datetime包进行对时间的一些操作
2016/06/23 Python
python 实现在txt指定行追加文本的方法
2018/04/29 Python
Python利用逻辑回归模型解决MNIST手写数字识别问题详解
2020/01/14 Python
Python如何避免文件同名产生覆盖
2020/06/09 Python
Python数据可视化实现漏斗图过程图解
2020/07/20 Python
CSS3中HSL和HSLA的简单使用示例
2015/07/14 HTML / CSS
html5标记文字_动力节点Java学院整理
2017/07/11 HTML / CSS
NUK奶瓶美国官网:NUK美国
2016/09/26 全球购物
团队口号大全
2014/06/06 职场文书
PHP中->和=>的意思
2021/03/31 PHP
浅谈MySQL表空间回收的正确姿势
2021/10/05 MySQL
mysql5.7的安装及Navicate长久免费使用的实现过程
2021/11/17 MySQL
vue css 相对路径导入问题级踩坑记录
2022/06/05 Vue.js