版权声明:本文为博主原创文章,未经博主允许不得转载。

 
 

虽然常说做C/C++编程的程序员转做PHP编程很快可以上手,但是对于PHP中的引用和C++的差别比较大,这种差别更多是由于C++和PHP的变量存储结构不同造成的,本文试图详解一下PHP中的引用,对C++中的引用只是作对比时提及,如果要了解C++的引用请参考《C++ Primer》一书。理解本文最好先看一下笔者PHP变量存储结构的博文变量赋值行为的博文,本文说明PHP引用特别是对象引用和函数返回引用容易被错误使用和错误理解的地方。

1.简单变量引用

对于PHP中的引用,我们先从基本的变量谈起,用之前的程序说话,运行如下PHP程序

  1. <?php
  2. $a = 1;
  3. $b = $a;
  4. echo '$a='.$a."\t".'$b='.$b."\n";
  5. $b = 2;
  6. echo '$a='.$a."\t".'$b='.$b."\n";
  7. $c = 1;
  8. $d = &$c;
  9. echo '$c='.$c."\t".'$d='.$d."\n";
  10. $d = 2;
  11. echo '$c='.$c."\t".'$d='.$d."\n";
  12. ?>

这里采用命令行演示,结果如下

  1. $a=1 $b=1
  2. $a=1 $b=2
  3. $c=1 $d=1
  4. $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中的引用概念来说,这样的理解完全是错误的,测试代码如下

  1. <?php
  2. class class1
  3. {
  4. public $a;
  5. function __construct()
  6. {
  7. $this->a = 1;
  8. }
  9. }
  10. class class2
  11. {
  12. public $a;
  13. function __construct()
  14. {
  15. $this->a = 2;
  16. }
  17. }
  18. $object1 = new class1;
  19. $object2 = new class2;
  20. $a = $object1;
  21. $b = $a;
  22. echo '$a->a='.$a->a."\t".'$b->a='.$b->a."\t".'$a属于类'.get_class($a)."\t".'$b属于类'.get_class($b)."\n";
  23. $b->a = 3;
  24. echo '$a->a='.$a->a."\t".'$b->a='.$b->a."\t".'$a属于类'.get_class($a)."\t".'$b属于类'.get_class($b)."\n";
  25. $b = $object2;
  26. echo '$a->a='.$a->a."\t".'$b->a='.$b->a."\t".'$a属于类'.get_class($a)."\t".'$b属于类'.get_class($b)."\n";
  27. echo "\n";
  28. $c = $object1;
  29. $d = &$c;
  30. echo '$c->a='.$c->a."\t".'$d->a='.$d->a."\t".'$c属于类'.get_class($c)."\t".'$d属于类'.get_class($d)."\n";
  31. $d->a = 4;
  32. echo '$c->a='.$c->a."\t".'$d->a='.$d->a."\t".'$c属于类'.get_class($c)."\t".'$d属于类'.get_class($d)."\n";
  33. $d = $object2;
  34. echo '$c->a='.$c->a."\t".'$d->a='.$d->a."\t".'$c属于类'.get_class($c)."\t".'$d属于类'.get_class($d)."\n";
  35. ?>

执行结果如下

  1. $a->a=1 $b->a=1 $a属于类class1  $b属于类class1
  2. $a->a=3 $b->a=3 $a属于类class1  $b属于类class1
  3. $a->a=3 $b->a=2 $a属于类class1  $b属于类class2
  4. $c->a=3 $d->a=3 $c属于类class1  $d属于类class1
  5. $c->a=4 $d->a=4 $c属于类class1  $d属于类class1
  6. $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.函数参数传递

对于函数参数传递,有了前面对变量和对象的理解讲解,这里只给出测试代码,请自行分析,不懂请留言

  1. <?php
  2. //测试变量传递
  3. function changeByValue($a)
  4. {
  5. $a = 1;
  6. }
  7. function changeByRef(&$a)
  8. {
  9. $a = 1;
  10. }
  11. $a = 2;
  12. echo '$a before pass to changeByValue='.$a."\n";
  13. changeByValue($a);
  14. echo '$a after pass to changeByValue='.$a."\n";
  15. $a = 2;
  16. echo '$a before pass to changeByRef='.$a."\n";
  17. changeByRef($a);
  18. echo '$a after pass to changeByRef='.$a."\n";
  19. //测试对象传递
  20. class class1
  21. {
  22. public $a;
  23. function __construct()
  24. {
  25. $this->a = 1;
  26. }
  27. }
  28. class class2
  29. {
  30. public $a;
  31. function __construct()
  32. {
  33. $this->a = 2;
  34. }
  35. }
  36. function changeByValueClass(class1 $a)
  37. {
  38. $a->a = 3;
  39. global $object1,$object2;
  40. echo '此时还没有对对象重新赋值,只是操作对象属性'."\n";
  41. echo '$a->a='.$a->a."\t".'$object1->a='.$object1->a."\t".'$a属于类'.get_class($a)."\t".'$obejct1属于类'.get_class($object1)."\n";
  42. echo '此时对对象重新赋值'."\n";
  43. $a = $object2;
  44. echo '$a->a='.$a->a."\t".'$object1->a='.$object1->a."\t".'$a属于类'.get_class($a)."\t".'$obejct1属于类'.get_class($object1)."\n";
  45. }
  46. function changeByRefClass(class1 &$a)
  47. {
  48. $a->a = 4;
  49. global $object1,$object2;
  50. echo '此时还没有对对象重新赋值,只是操作对象属性'."\n";
  51. echo '$a->a='.$a->a."\t".'$object1->a='.$object1->a."\t".'$a属于类'.get_class($a)."\t".'$obejct1属于类'.get_class($object1)."\n";
  52. echo '此时对对象重新赋值'."\n";
  53. $a = $object2;
  54. echo '$a->a='.$a->a."\t".'$object1->a='.$object1->a."\t".'$a属于类'.get_class($a)."\t".'$obejct1属于类'.get_class($object1)."\n";
  55. }
  56. $object1 = new class1;
  57. $object2 = new class2;
  58. changeByValueClass($object1);
  59. changeByRefClass($object1);
  60. ?>

执行结果如下

  1. $a before pass to changeByValue=2
  2. $a after pass to changeByValue=2
  3. $a before pass to changeByRef=2
  4. $a after pass to changeByRef=1
  5. 此时还没有对对象重新赋值,只是操作对象属性
  6. $a->a=3 $object1->a=3   $a属于类class1  $obejct1属于类class1
  7. 此时对对象重新赋值
  8. $a->a=2 $object1->a=3   $a属于类class2  $obejct1属于类class1
  9. 此时还没有对对象重新赋值,只是操作对象属性
  10. $a->a=4 $object1->a=4   $a属于类class1  $obejct1属于类class1
  11. 此时对对象重新赋值
  12. $a->a=2 $object1->a=2   $a属于类class2  $obejct1属于类class2

简单说明一下,当传入简单变量时必须明确指明是传值方式还是传引用方式调用参数,如果要求在函数中形参的重新赋值影响到实参就需要引用方式传值,反之就直接传值就行,特别提一下,一般对象传值就行了因为一般要求改变也只是对对象成员属性进行改变很少需要改变对象赋值。

4.函数返回引用

PHP的函数返回引用也是很多人有疑惑的地方,PHP的函数返回引用不仅在函数定义的时候要说明返回的是引用,在调用的时候也要说明以引用的方式调用,缺一不可。这里以函数返回值为简单变量的情况来测试代码,前面已经说过了对象和普通变量引用没什么差别,不过和函数参数调用一样,一般情况下也不需要返回对象引用。

  1. <?php
  2. $a = 1;
  3. function returnValue()
  4. {
  5. global $a;
  6. return $a;
  7. }
  8. echo "函数返回值,值方式调用函数\n";
  9. $b = returnValue();
  10. debug_zval_dump($a);
  11. debug_zval_dump($b);
  12. echo '$a='.$a."\t".'$b='.$b."\n";
  13. $b=2;
  14. debug_zval_dump($a);
  15. debug_zval_dump($b);
  16. echo '$a='.$a."\t".'$b='.$b."\n";
  17. echo "函数返回值,引用方式调用函数\n";
  18. $b = &returnValue();
  19. debug_zval_dump($a);
  20. debug_zval_dump($b);
  21. echo '$a='.$a."\t".'$b='.$b."\n";
  22. $b=2;
  23. debug_zval_dump($a);
  24. debug_zval_dump($b);
  25. echo '$a='.$a."\t".'$b='.$b."\n";
  26. $a = 1;
  27. function &returnRef()
  28. {
  29. global $a;
  30. return $a;
  31. }
  32. echo "函数返回引用,值方式调用函数\n";
  33. $b = returnRef();
  34. debug_zval_dump($a);
  35. debug_zval_dump($b);
  36. echo '$a='.$a."\t".'$b='.$b."\n";
  37. $b=2;
  38. debug_zval_dump($a);
  39. debug_zval_dump($b);
  40. echo '$a='.$a."\t".'$b='.$b."\n";
  41. echo "函数返回引用,引用方式调用函数\n";
  42. $b = &returnRef();
  43. debug_zval_dump($a);
  44. debug_zval_dump($b);
  45. echo '$a='.$a."\t".'$b='.$b."\n";
  46. $b=2;
  47. debug_zval_dump($a);
  48. debug_zval_dump($b);
  49. echo '$a='.$a."\t".'$b='.$b."\n";
  50. ?>

执行结果如下

  1. 函数返回值,值方式调用函数
  2. long(1) refcount(2)
  3. long(1) refcount(2)
  4. $a=1    $b=1
  5. long(1) refcount(2)
  6. long(2) refcount(2)
  7. $a=1    $b=2
  8. 函数返回值,引用方式调用函数
  9. long(1) refcount(2)
  10. long(1) refcount(2)
  11. $a=1    $b=1
  12. long(1) refcount(2)
  13. long(2) refcount(2)
  14. $a=1    $b=2
  15. 函数返回引用,值方式调用函数
  16. long(1) refcount(3)
  17. long(1) refcount(3)
  18. $a=1    $b=1
  19. long(1) refcount(2)
  20. long(2) refcount(2)
  21. $a=1    $b=2
  22. 函数返回引用,引用方式调用函数
  23. long(1) refcount(1)
  24. long(1) refcount(1)
  25. $a=1    $b=1
  26. long(2) refcount(1)
  27. long(2) refcount(1)

可以看到对于函数返回简单变类型的情况,只有函数返回的是引用,同时以引用方式调用函数时才能真正起到引用的效果。

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

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

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

原创,转载请注明来自http://blog.csdn.net/wenzhou1219

深入PHP中的引用的更多相关文章

  1. php中关于引用(&)详解

    php中关于引用(&)详解 php的引用(就是在变量或者函数.对象等前面加上&符号) 在PHP 中引用的意思是:不同的变量名访问同一个变量内容. 与C语言中的指针是有差别的.C语言中的 ...

  2. 浅谈Java中的引用

    在Java语言中,引用是指,某一个数据,代表的是另外一块内存的的起始地址,那么我们就称这个数据为引用. 在JVM中,GC回收的大致准则,是认定如果不能从根节点,根据引用的不断传递,最终指向到一块内存区 ...

  3. error LNK2019: 无法解析的外部符号 _WinMain@16,该符号在函数 ___tmainCRTStartup 中被引用

    MSVCRTD.lib(crtexew.obj) : error LNK2019: 无法解析的外部符号 _WinMain@16,该符号在函数 ___tmainCRTStartup 中被引用 Debug ...

  4. c++中的引用与指针的区别

    http://blog.csdn.net/lyd_253261362/article/details/4323691 c++中的引用与指针的区别 ★ 相同点: 1. 都是地址的概念: 指针指向一块内存 ...

  5. CSAPP读书随笔之一:为什么汇编器会将call指令中的引用的初始值设置为-4

    CSAPP,即<深入理解计算机系统:程序员视角>第三版,是一本好书,但读起来确需要具备相当的基本功.而且,有的表述(中译文)还不太直白. 比如,第463页提到,(对于32位系统)为什么汇编 ...

  6. 转 mvc项目中,解决引用jquery文件后智能提示失效的办法

    mvc项目中,解决用Url.Content方法引用jquery文件后智能提示失效的办法   这个标题不知道要怎么写才好, 但是希望文章的内容对大家有帮助. 场景如下: 我们在用开发开发程序的时候,经常 ...

  7. 内部类访问外部类的变量必须是final吗,java静态方法中不能引用非静态变量,静态方法中不能创建内部类的实例

    内部类访问外部类的变量必须是final吗? 如下: package com.java.concurrent; class A { int i = 3; public void shout() { cl ...

  8. 理解Java中的引用传递和值传递

    关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题,有论坛说Java中只有值传递,也有些地方说引用传递和值传递都存在,比较容易让人迷惑.关于值传递和引用传递其实需要分情况看待,今天学习 ...

  9. VB6中的引用传递 与 VB.NET中的引用传递的区别

    首先注意一点,在VB6中缺省参数传递的方式是:引用传递,而在VB.NET中缺省参数传递的方式是:值传递. 然后我们看下面VB6中的引用传递与VB.NET中的引用传递的对比. VB6中的引用传递 Pri ...

  10. [转] 深入探讨C++中的引用

    引用是C++引入的新语言特性,是C++常用的一个重要内容之一,正确.灵活地使用引用,可以使程序简洁.高效.我在工作中发现,许多人使用它仅仅是想当然,在某些微妙的场合,很容易出错,究其原由,大多因为没有 ...

随机推荐

  1. Android ContentProvider的介绍(很详细)

    博客分类: android进阶   一.ContentProvider的概念 ContentProvider:为存储和获取数据提供统一的接口.可以在不同的应用程序之间共享数据.Android已经为常见 ...

  2. hdu 5242——Game——————【树链剖分思想】

    Game Time Limit:1500MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Submit Status  ...

  3. SpringSecurity 3.2入门(10)自定义权限控制认证及授权的过程

    上一章的代码实现还存在一些问题,如角色表.权限表的用处没有体现出来,但是已经能完成URL拦截功能,后面将会继续完善认证及授权的过程. 认证及授权的过程如下: 1.容器启动,MyInvocationSe ...

  4. 自己用jquery+css+div写的一个弹窗

    弹窗支持两种模式,一种是普通信息提示框,调用方法:popup.msgPopup(msg); 另一种是可以加载页面的弹窗,调用方法:popup.pagePopup(url); 效果图: css代码 ;; ...

  5. [LeetCode]24. Swap Nodes in Pairs两两交换链表中的节点

    Given a linked list, swap every two adjacent nodes and return its head. Example: Given 1->2->3 ...

  6. Baseball Game

    You're now a baseball game point recorder. Given a list of strings, each string can be one of the 4 ...

  7. Csharp:TinyMCE HTML Editor in .NET WindowsForms

    /// <summary> /// /// </summary> public partial class Form2 : Form { private mshtml.IHTM ...

  8. 原生js封装十字参考线插件(一)

    需求来源: 拓扑图之机房平面图,显示机房长宽比例尺,房间内标注各种设备间距不易实现,特在机房平面图上层加一个十字参考线 横竖两条线垂直,在鼠标指针处交叉,显示鼠标指针坐标(相对机房平面图的坐标,不是相 ...

  9. eclipse的应用和整理

    1如何在eclipse中获取动态项目的绝对路径 1.鼠标选中项目,右击菜单,选择properties2.出来弹出框,选择resource,location的值就是你想要的项目绝对路径 JSP中获得当前 ...

  10. hibernate基础配置

    数据库表名和类名 一致 注解:可写可不写: XML:可写可不写: <class name="Student"> 不一致 注解:  public class Teache ...