PHP——5(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时加上了一个引用定义符&。
这里我们用一张图来说明

image.png

最开始将$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要设计成只有函数返回的是引用同时以引用方式调用函数时才能真正起到引用的效果,这不是很麻烦吗?一切都得从根源说起--变量存储结构。
根据以上刺探结果分析绘制内存变化过程示意图如下:

image.png

从图上看,当函数直接返回时会将要返回的变量内存结构复制一份成匿名结构体(注意此时引用计数refcount为0),然后这里无论使用=还是=&调用函数结果都是将$b指向这个结构体;当函数引用返回时会将要返回的变量内存结构的指针,这时候使用=调用函数结果和直接赋值$a=$b一样,当使用=&调用函数时和引用赋值$a=&$b表现一样。所以说,这里要弄清楚的是,函数返回方式决定了对要返回的变量的处理方式(复制内存还是直接返回内存指针),函数调用方式决定了待赋值量和返回量的结合方式(指向同一内存结构还是绑定)。

好了,总结一下本博文最重要的两点:对象引用表现和普通变量一样,函数返回方式和调用方式在函数返回引用中各施其职,弄懂这两点对你加深PHP的理解和写出更好的PHP程序相信都是有帮助的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • 一、php可以做什么 php是一种可以在服务器端运行的编程语言,可以运行在Web服务器端。 php是一门后台编程语...
    空谷悠阅读 3,091评论 4 97
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,213评论 11 349
  • __block和__weak修饰符的区别其实是挺明显的:1.__block不管是ARC还是MRC模式下都可以使用,...
    LZM轮回阅读 3,292评论 0 6
  • 问题场景 在一台服务器中部署了多个站点,站点A使用了80端口,站点B(Discuz)使用了81端口,并使用了反向代...
    lancely阅读 2,629评论 0 2
  • 亲爱的,似乎好久都没有再想起你,不止欢笑,不止悲戚,好像真的忘记了你。亲爱的,似乎我又想起了你,既不清晰,仿佛模糊...
    枟辰阅读 208评论 0 3