深入PHP中的引用
版权声明:本文为博主原创文章,未经博主允许不得转载。
虽然常说做C/C++编程的程序员转做PHP编程很快可以上手,但是对于PHP中的引用和C++的差别比较大,这种差别更多是由于C++和PHP的变量存储结构不同造成的,本文试图详解一下PHP中的引用,对C++中的引用只是作对比时提及,如果要了解C++的引用请参考《C++ Primer》一书。理解本文最好先看一下笔者PHP变量存储结构的博文和变量赋值行为的博文,本文说明PHP引用特别是对象引用和函数返回引用容易被错误使用和错误理解的地方。
1.简单变量引用
对于PHP中的引用,我们先从基本的变量谈起,用之前的程序说话,运行如下PHP程序
- <?php
- $a = 1;
- $b = $a;
- echo '$a='.$a."\t".'$b='.$b."\n";
- $b = 2;
- echo '$a='.$a."\t".'$b='.$b."\n";
- $c = 1;
- $d = &$c;
- echo '$c='.$c."\t".'$d='.$d."\n";
- $d = 2;
- echo '$c='.$c."\t".'$d='.$d."\n";
- ?>
这里采用命令行演示,结果如下
- $a=1 $b=1
- $a=1 $b=2
- $c=1 $d=1
- $c=2 $d=2
可以看到:
开始定义$a时初始化为1,定义$b时初始化为$a的值,这时候两个值均为1,下面改变$b的值为2,再次输出,可以看到$b的值发生变化为2而$a的值仍然为1,这时候$a,$b的值不一样;然后,开始定义$c时初始化为1,定义$d时初始化为$c的引用,这时候两个值均为1,下面改变$d的值为2,再次输出,可以看到$d的值发生变化为2而$c的值也为2,这时候$c,$d的值一样
这两个例子的唯一不同在于定义$d时加上了一个引用定义符&。
这里我们用一张图来说明
最开始将$a=1时,$a变量指向内存结构1,$a(内存结构1)的引用计数refcount为1,传值方式赋值给$b后,$a(内存结构1)的引用计数refcount+1为2,由于不是引用故is_ref值为0,$a、$b指向同一内存结构,故此时$b的引用计数refcount也为2而is_ref为0,当$b赋值为2时,检查是否为引用is_ref为0且引用计数refcount>1故新建一个内存结构2其值为2,$b指向这个内存结构,$b的的引用计数refcount为1而is_ref为0,$a的引用计数refcount-1为1而is_ref为0;最开始将$c=1时,$c变量指向内存结构1,$c(内存结构1)的引用计数refcount为1,引用方式赋值为$d后,由于是引用故is_ref值为1,$c(内存结构1)的引用计数refcount设为0,$c、$d指向同一内存结构,故此时$d的引用计数refcount也为0而is_ref为1,当$d赋值为2时,检查是否为引用is_ref为1,不会新建一个内存结构,只是将原来$a指向的内存结构的值改为2,其他不变。
2.对象引用
PHP的对象引用是人们最容易错误理解的地方,由于PHP赋值的"Copy on Write"原则,将一个对象赋值给另外一个对象后两者指向同一个内存地址,这时候去操纵其中一个对象的成员变量实际上就是操作另外一个对象的成员变量,这样由于PHP存储和赋值行为决定的表现和C++中的引用即别名表现是一样的,人们看到赋值后对象这样的表现就说PHP中对象赋值默认传的是引用,显然按照PHP中的引用概念来说,这样的理解完全是错误的,测试代码如下
- <?php
- class class1
- {
- public $a;
- function __construct()
- {
- $this->a = 1;
- }
- }
- class class2
- {
- public $a;
- function __construct()
- {
- $this->a = 2;
- }
- }
- $object1 = new class1;
- $object2 = new class2;
- $a = $object1;
- $b = $a;
- echo '$a->a='.$a->a."\t".'$b->a='.$b->a."\t".'$a属于类'.get_class($a)."\t".'$b属于类'.get_class($b)."\n";
- $b->a = 3;
- echo '$a->a='.$a->a."\t".'$b->a='.$b->a."\t".'$a属于类'.get_class($a)."\t".'$b属于类'.get_class($b)."\n";
- $b = $object2;
- echo '$a->a='.$a->a."\t".'$b->a='.$b->a."\t".'$a属于类'.get_class($a)."\t".'$b属于类'.get_class($b)."\n";
- echo "\n";
- $c = $object1;
- $d = &$c;
- echo '$c->a='.$c->a."\t".'$d->a='.$d->a."\t".'$c属于类'.get_class($c)."\t".'$d属于类'.get_class($d)."\n";
- $d->a = 4;
- echo '$c->a='.$c->a."\t".'$d->a='.$d->a."\t".'$c属于类'.get_class($c)."\t".'$d属于类'.get_class($d)."\n";
- $d = $object2;
- echo '$c->a='.$c->a."\t".'$d->a='.$d->a."\t".'$c属于类'.get_class($c)."\t".'$d属于类'.get_class($d)."\n";
- ?>
执行结果如下
- $a->a=1 $b->a=1 $a属于类class1 $b属于类class1
- $a->a=3 $b->a=3 $a属于类class1 $b属于类class1
- $a->a=3 $b->a=2 $a属于类class1 $b属于类class2
- $c->a=3 $d->a=3 $c属于类class1 $d属于类class1
- $c->a=4 $d->a=4 $c属于类class1 $d属于类class1
- $c->a=2 $d->a=2 $c属于类class2 $d属于类class2
可以看到,$b=$a赋值后改变$b->a的值$a->a也发生了变化,人们一般这时候就认为PHP对象赋值传的是引用,但是我们在PHP中的引用意思是对变量赋值后其关联的引用对象也发生变化,所以,$b=$object2发现$a没有改变,显然这时候并不是引用;$d=&$c才是真正的引用,$d=$object2后$a也发生改变等于$object2。
我们引用PHP官网上的一段话:
“在php5 的对象编程经常提到的一个关键点是“默认情况下对象是通过引用传递的”。但其实这不是完全正确的。下面通过一些例子来说明。
php的引用是别名,就是两个不同的变量名字指向相同的内容。在php5,一个对象变量已经不再保存整个对象的值。只是保存一个标识符来访问真正的对象内容。 当对象作为参数传递,作为结果返回,或者赋值给另外一个变量,另外一个变量跟原来的不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容。”
所以说,其实对象赋值行为和普通变量是一样的,两者的引用表现也是完全一样的。但是一般来说我们更多使用的是对象直接赋值,这样改变其中一个对象属性另一个对象属性也会发生变化,表现的像C++中的引用一样,这样就够了。
3.函数参数传递
对于函数参数传递,有了前面对变量和对象的理解讲解,这里只给出测试代码,请自行分析,不懂请留言
- <?php
- //测试变量传递
- function changeByValue($a)
- {
- $a = 1;
- }
- function changeByRef(&$a)
- {
- $a = 1;
- }
- $a = 2;
- echo '$a before pass to changeByValue='.$a."\n";
- changeByValue($a);
- echo '$a after pass to changeByValue='.$a."\n";
- $a = 2;
- echo '$a before pass to changeByRef='.$a."\n";
- changeByRef($a);
- echo '$a after pass to changeByRef='.$a."\n";
- //测试对象传递
- class class1
- {
- public $a;
- function __construct()
- {
- $this->a = 1;
- }
- }
- class class2
- {
- public $a;
- function __construct()
- {
- $this->a = 2;
- }
- }
- function changeByValueClass(class1 $a)
- {
- $a->a = 3;
- global $object1,$object2;
- echo '此时还没有对对象重新赋值,只是操作对象属性'."\n";
- echo '$a->a='.$a->a."\t".'$object1->a='.$object1->a."\t".'$a属于类'.get_class($a)."\t".'$obejct1属于类'.get_class($object1)."\n";
- echo '此时对对象重新赋值'."\n";
- $a = $object2;
- echo '$a->a='.$a->a."\t".'$object1->a='.$object1->a."\t".'$a属于类'.get_class($a)."\t".'$obejct1属于类'.get_class($object1)."\n";
- }
- function changeByRefClass(class1 &$a)
- {
- $a->a = 4;
- global $object1,$object2;
- echo '此时还没有对对象重新赋值,只是操作对象属性'."\n";
- echo '$a->a='.$a->a."\t".'$object1->a='.$object1->a."\t".'$a属于类'.get_class($a)."\t".'$obejct1属于类'.get_class($object1)."\n";
- echo '此时对对象重新赋值'."\n";
- $a = $object2;
- echo '$a->a='.$a->a."\t".'$object1->a='.$object1->a."\t".'$a属于类'.get_class($a)."\t".'$obejct1属于类'.get_class($object1)."\n";
- }
- $object1 = new class1;
- $object2 = new class2;
- changeByValueClass($object1);
- changeByRefClass($object1);
- ?>
执行结果如下
- $a before pass to changeByValue=2
- $a after pass to changeByValue=2
- $a before pass to changeByRef=2
- $a after pass to changeByRef=1
- 此时还没有对对象重新赋值,只是操作对象属性
- $a->a=3 $object1->a=3 $a属于类class1 $obejct1属于类class1
- 此时对对象重新赋值
- $a->a=2 $object1->a=3 $a属于类class2 $obejct1属于类class1
- 此时还没有对对象重新赋值,只是操作对象属性
- $a->a=4 $object1->a=4 $a属于类class1 $obejct1属于类class1
- 此时对对象重新赋值
- $a->a=2 $object1->a=2 $a属于类class2 $obejct1属于类class2
简单说明一下,当传入简单变量时必须明确指明是传值方式还是传引用方式调用参数,如果要求在函数中形参的重新赋值影响到实参就需要引用方式传值,反之就直接传值就行,特别提一下,一般对象传值就行了因为一般要求改变也只是对对象成员属性进行改变很少需要改变对象赋值。
4.函数返回引用
PHP的函数返回引用也是很多人有疑惑的地方,PHP的函数返回引用不仅在函数定义的时候要说明返回的是引用,在调用的时候也要说明以引用的方式调用,缺一不可。这里以函数返回值为简单变量的情况来测试代码,前面已经说过了对象和普通变量引用没什么差别,不过和函数参数调用一样,一般情况下也不需要返回对象引用。
- <?php
- $a = 1;
- function returnValue()
- {
- global $a;
- return $a;
- }
- echo "函数返回值,值方式调用函数\n";
- $b = returnValue();
- debug_zval_dump($a);
- debug_zval_dump($b);
- echo '$a='.$a."\t".'$b='.$b."\n";
- $b=2;
- debug_zval_dump($a);
- debug_zval_dump($b);
- echo '$a='.$a."\t".'$b='.$b."\n";
- echo "函数返回值,引用方式调用函数\n";
- $b = &returnValue();
- debug_zval_dump($a);
- debug_zval_dump($b);
- echo '$a='.$a."\t".'$b='.$b."\n";
- $b=2;
- debug_zval_dump($a);
- debug_zval_dump($b);
- echo '$a='.$a."\t".'$b='.$b."\n";
- $a = 1;
- function &returnRef()
- {
- global $a;
- return $a;
- }
- echo "函数返回引用,值方式调用函数\n";
- $b = returnRef();
- debug_zval_dump($a);
- debug_zval_dump($b);
- echo '$a='.$a."\t".'$b='.$b."\n";
- $b=2;
- debug_zval_dump($a);
- debug_zval_dump($b);
- echo '$a='.$a."\t".'$b='.$b."\n";
- echo "函数返回引用,引用方式调用函数\n";
- $b = &returnRef();
- debug_zval_dump($a);
- debug_zval_dump($b);
- echo '$a='.$a."\t".'$b='.$b."\n";
- $b=2;
- debug_zval_dump($a);
- debug_zval_dump($b);
- echo '$a='.$a."\t".'$b='.$b."\n";
- ?>
执行结果如下
- 函数返回值,值方式调用函数
- long(1) refcount(2)
- long(1) refcount(2)
- $a=1 $b=1
- long(1) refcount(2)
- long(2) refcount(2)
- $a=1 $b=2
- 函数返回值,引用方式调用函数
- long(1) refcount(2)
- long(1) refcount(2)
- $a=1 $b=1
- long(1) refcount(2)
- long(2) refcount(2)
- $a=1 $b=2
- 函数返回引用,值方式调用函数
- long(1) refcount(3)
- long(1) refcount(3)
- $a=1 $b=1
- long(1) refcount(2)
- long(2) refcount(2)
- $a=1 $b=2
- 函数返回引用,引用方式调用函数
- long(1) refcount(1)
- long(1) refcount(1)
- $a=1 $b=1
- long(2) refcount(1)
- long(2) refcount(1)
可以看到对于函数返回简单变类型的情况,只有函数返回的是引用,同时以引用方式调用函数时才能真正起到引用的效果。
那么为什么PHP要设计成只有函数返回的是引用同时以引用方式调用函数时才能真正起到引用的效果,这不是很麻烦吗?一切都得从根源说起--变量存储结构。
根据以上刺探结果分析绘制内存变化过程示意图如下:
从图上看,当函数直接返回时会将要返回的变量内存结构复制一份成匿名结构体(注意此时引用计数refcount为0),然后这里无论使用=还是=&调用函数结果都是将$b指向这个结构体;当函数引用返回时会将要返回的变量内存结构的指针,这时候使用=调用函数结果和直接赋值$a=$b一样,当使用=&调用函数时和引用赋值$a=&$b表现一样。所以说,这里要弄清楚的是,函数返回方式决定了对要返回的变量的处理方式(复制内存还是直接返回内存指针),函数调用方式决定了待赋值量和返回量的结合方式(指向同一内存结构还是绑定)。
好了,总结一下本博文最重要的两点:对象引用表现和普通变量一样,函数返回方式和调用方式在函数返回引用中各施其职,弄懂这两点对你加深PHP的理解和写出更好的PHP程序相信都是有帮助的。
原创,转载请注明来自http://blog.csdn.net/wenzhou1219
深入PHP中的引用的更多相关文章
- php中关于引用(&)详解
php中关于引用(&)详解 php的引用(就是在变量或者函数.对象等前面加上&符号) 在PHP 中引用的意思是:不同的变量名访问同一个变量内容. 与C语言中的指针是有差别的.C语言中的 ...
- 浅谈Java中的引用
在Java语言中,引用是指,某一个数据,代表的是另外一块内存的的起始地址,那么我们就称这个数据为引用. 在JVM中,GC回收的大致准则,是认定如果不能从根节点,根据引用的不断传递,最终指向到一块内存区 ...
- error LNK2019: 无法解析的外部符号 _WinMain@16,该符号在函数 ___tmainCRTStartup 中被引用
MSVCRTD.lib(crtexew.obj) : error LNK2019: 无法解析的外部符号 _WinMain@16,该符号在函数 ___tmainCRTStartup 中被引用 Debug ...
- c++中的引用与指针的区别
http://blog.csdn.net/lyd_253261362/article/details/4323691 c++中的引用与指针的区别 ★ 相同点: 1. 都是地址的概念: 指针指向一块内存 ...
- CSAPP读书随笔之一:为什么汇编器会将call指令中的引用的初始值设置为-4
CSAPP,即<深入理解计算机系统:程序员视角>第三版,是一本好书,但读起来确需要具备相当的基本功.而且,有的表述(中译文)还不太直白. 比如,第463页提到,(对于32位系统)为什么汇编 ...
- 转 mvc项目中,解决引用jquery文件后智能提示失效的办法
mvc项目中,解决用Url.Content方法引用jquery文件后智能提示失效的办法 这个标题不知道要怎么写才好, 但是希望文章的内容对大家有帮助. 场景如下: 我们在用开发开发程序的时候,经常 ...
- 内部类访问外部类的变量必须是final吗,java静态方法中不能引用非静态变量,静态方法中不能创建内部类的实例
内部类访问外部类的变量必须是final吗? 如下: package com.java.concurrent; class A { int i = 3; public void shout() { cl ...
- 理解Java中的引用传递和值传递
关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题,有论坛说Java中只有值传递,也有些地方说引用传递和值传递都存在,比较容易让人迷惑.关于值传递和引用传递其实需要分情况看待,今天学习 ...
- VB6中的引用传递 与 VB.NET中的引用传递的区别
首先注意一点,在VB6中缺省参数传递的方式是:引用传递,而在VB.NET中缺省参数传递的方式是:值传递. 然后我们看下面VB6中的引用传递与VB.NET中的引用传递的对比. VB6中的引用传递 Pri ...
- [转] 深入探讨C++中的引用
引用是C++引入的新语言特性,是C++常用的一个重要内容之一,正确.灵活地使用引用,可以使程序简洁.高效.我在工作中发现,许多人使用它仅仅是想当然,在某些微妙的场合,很容易出错,究其原由,大多因为没有 ...
随机推荐
- 关于日常使用Azure MySQL中遇到的连接问题以及排查方法分享
由于防火墙问题,TCP keep alive 问题,以及 MySQL 自身的参数问题这三个在使用中比较常见,所以今天就分享下自己找到的排查方法. 今天先聊一聊防火墙问题 大多数人在第一次创建 MySQ ...
- DJango小总结二
1.Django请求的生命周期 武彦涛: 路由系统 -> 试图函数(获取模板+数据=>渲染) -> 字符串返回给用户 2.路由系统 王腾: ...
- MyBatis 中 sqlmapconfig核心标签说明以及配置
文件介绍 对于 MyBatis 最核心的全局配置文件是 sqlmapConfig.xml 文件,其中包含了数据库的连接配置信息.Mapper 映射文件的加载路径.全局参数.类型别名等. 配置项详解 标 ...
- git丢弃修改
丢弃本地所有更改: git fetch origin git reset --hard origin/[对应的分支] 更改上次提交的注释: git commit --amend 会进到(vim编 ...
- windows环境下wampserver配置https
因为公司业务主要是在微信上进行开展的,所以作为程序员的我们每天的开发任务就都是在与微信打交道,这个时候我们就需要在本地配置端口映射到外网,方便我们在微信客户端进行调试. 最近某种需要,所以需要配置 h ...
- Android 环信(Android)设置头像和昵称的方法
最近,经常有朋友问到,如何集成环信头像,怎么才能快速显示头像,因时间紧急,很多朋友都没有时间慢慢的研究代码,这里大家稍微花10分钟看一下文章,看完后再花5分钟改一下代码,即可达到你们所要的效果. 当然 ...
- PhoneGap API介绍:Events
事件类型: backbutton deviceready menubutton pause resume searchbutton online offline backbutton 当用户在Andr ...
- RBG灯颜色渐变(颜色要尽可能多)程序分析
相信很多调过RBG灯的朋友都是通过分别改变R.B.G的占空比来改变颜色的,但是不是发现了一个问题,那就是不管怎样调都很难实现几十种颜色的变化,一般只有是7种颜色的渐变.下面给朋友们分享一个可以实现几十 ...
- table是可语义化
为了使我们的网站更好的被搜索引擎抓取收录,更自然的获得更高的流量,网站标签的语义化就显得尤为重要.所谓标签语义化,就是指标签的含义. 为了更好的理解标签的语义化,先看下面这个例子: <table ...
- asyncio标准库4 asyncio performance
性能包括2部分 每秒并发请求数(Number of concurrent requests per second) 每秒请求负载(Request latency in seconds: min/ave ...