关于"引用&"的用法.究竟什么时候用,什么时候不该?- 中国Unix技术社区

发表于:2007-05-25来源:作者:点击数: 标签:amp什么时候quot用法引用
在论坛看了很多文章,很多高手提到了PHP的"引用""nbsp;夜猫子 回复于:2004-03-25 15:59:45 这是个很好的话题,期待longnetpro发表看法,其实我也需要好好学一下 dualface 回复于:2004-03-25 16:42:53 简单的说下面几种情况要使用引用 1、当你需要在函数中修

论坛看了很多文章,很多高手提到了PHP的"引用"  "&"
对这个问题我在PHP手册看了"引用的解释"一节,有点概念了
可是还是不知道在什么时候或地方应该使用"引用"来提高性能,什么时候或地方不应该用"引用"呢?
谢谢大家了

 夜猫子 回复于:2004-03-25 15:59:45
这是个很好的话题,期待longnetpro发表看法,其实我也需要好好学一下

 dualface 回复于:2004-03-25 16:42:53
简单的说下面几种情况要使用引用

1、当你需要在函数中修改传入参数本身的值:

[code:1:e8cd69fe63]
function test(& $value) {
$value = 1234;
}

$x = 1;
echo "x=$x<BR />";
test($x);
echo "x=$x<BR />";
[/code:1:e8cd69fe63]

2、通过函数返回某个值时不希望产生对象的另一个拷贝:

[code:1:e8cd69fe63]
class myobject
{
var $_name = null;
function myobject($name) {
$this->_name = $name;
}

function toString() {
return $this->_name;
}
}

function & test($name) {
return new myobject($name);
}

$obj = & test('object');
echo $obj->toString();
[/code:1:e8cd69fe63]

要注意的是上面的倒数第二行代码如果改为

$obj = test('object');

就一样会产生一个拷贝。所以要想从函数返回引用,除了在申明函数时在函数名前面加上"&"外,
赋值的地方一样要记得使用"&"。一般来说我只在这两种情况使用引用。
Zend Development Environment中关于这个问题的说明:使用引用作为参数不但不能够提高性能,
反倒会降低性能。

至于实际使用时是不是真的降低了性能,没做过测试就不发表意见了。

 sports98 回复于:2004-03-25 17:23:50
[quote:7766615022]
class myobject 

   var $_name = null; 
   function myobject($name) { 
      $this->_name = $name; 
   } 
    
   function toString() { 
      return $this->_name; 
   } 


function & test($name) { 
   return new myobject($name); 


$obj = & test('object'); 
echo $obj->toString(); 
[/quote:7766615022]

我觉得,我们编写代码的时候总不能按编译语言的方式来处理PHP脚本或其他脚本。
[quote:7766615022]
class myobject 

   var $_name = null; 
   function myobject($name) { 
      $this->_name = $name; 
   } 
    
   function toString() { 
      return $this->_name; 
   } 


function test($name) { 
   return new myobject($name); 


$obj =test('object'); 
echo $obj->toString(); 
[/quote:7766615022]

我很想知道,去掉了&后我们所要获得的结果是否相同
如果相同的情况下,使用&对于PHP解释器来说应该是额外的增加了系统开销,毕竟PHP是运行在WEB服务的安全限制下的(当然了这点代码肯定还是没问题的),我们总不能拿编译语言的思维方式(不错使用了 & 是防止了对象的复制,单WEB脚本会在请求结束后释放掉我们所请求的内存空间)

&  我觉得应该是,为了在函数内修改非GLOBAL组中的数据而采用的方式

我个人的利用就是
[code:1:7766615022]
class DC
 {
function newclass($handle,$classname)
               {
                   //初始化新的方法返回
                    .....
}
 }
$P=new DCC;
//这里我将获得一个新的方法$NP;
$P->newclass(&$NP,"DCC.Cpp");
//run
$NP->run();
[/code:1:7766615022]

 sports98 回复于:2004-03-25 17:31:18
上面表达比较混乱,理解是

 类似C++内的引用或指针,引用单一的一个对象而不产生复制的另外一种值传递方式。

 使用场合:

         修改数据的时候希望将原始数据同时修改。

 infom 回复于:2004-03-25 22:34:45
需要修改这个数值的时候用 & 不需要修改的时候就不用。

如果不用,会产生 他的一份拷贝。理论上会多占一点点存储空间。

如果用了。则会修改他的值。如果需要用原值。就完戏了。

 shukebeita 回复于:2004-03-25 23:48:29
简单地说能用'&' 的时候,你就用'&',不能用的时候就不要用。

能用
一、对象的一致性。
如果你使用类和对象来编写程序的话,引用就变得非常重要了,它能够保证对象之间的赋值能够和其他语言的对象赋值相一致而不是产生一个对象的副本。在php5 中就没有这么麻烦了,它能够正确处理对象赋值,但是产生副本好想要用到一个clone函数才可以。

二、性能。
引用只是建立一个变量的别名。(虽然都说它不是指针,但是我想大多数情况下把它理解成类似指针的东西也没大关系,如果你知道指针是什么的话。)而通常情况下的变量赋值是需要进行copy操作的所以会慢一些。

下面是在别的论坛上和别人讨论时候写的测试代码,大家试一试就知道了谁快乐。至于dualface说的会降低性能,我还真不太清楚不知道在哪里看到,什么环境下会产生?
[size=20:1a9432e703][color=red:1a9432e703]修改,关于性能的观点是错误了。有的时候引用比较慢  :oops: 以前的例子由个低级错误,大家看后面的例子。实在抱歉(长在河边走,难免会湿鞋,贴子批多了也有副作用)[/color:1a9432e703][/size:1a9432e703]
什么时候引用不起作用
1、$a= & 'Bad sentence';//会报错
2、对于函数参数
function byRef(&$name)
{
...
}
你可以
$name = 'Shuke';
byRef($name);

但是不可以
byRef('Shuke');//会报错

3、对于函数引用方式的参数不能有默认值
function byRef(&$name='Shuke')//同样会报错
{
...//这个函数的定义是错误的。
}
这一点好像在最新的php5中变成了合法的。

4、还有unset也是挺有意思的,猜一猜下面的代码运行的结果是什么?
[code:1:1a9432e703]
$a = 1; 
$b =& $a; 
unset ($a); 
echo('b is'.$b.'<br>');

$a = 1; 
$b =& $a; 
unset ($b); 
echo('a is'.$a.'<br>');
[/code:1:1a9432e703]

 longnetpro 回复于:2004-03-26 07:04:24
其实要理解引用与值两个概念还是比较简单的。

用一个形象一点的比喻,一个PHP中的变量$a与它所代表的值是这样的关系:
变量名"$a"(注意这里$a只是一个符号而已,不代表任何值),而它的值比如是3的话,那么3就是放在你前面的一条鱼,而那个"$a"就是一把叉子,叉在那条鱼上,即$a与它的值就构成了一把叉子叉在一条鱼上。那么有另一个变量叫$b,它的值也是3,即$b=3,那么就是有另一把叉子叉在了*另*一条鱼上,注意强调是*另*一条鱼。意思是说,即使这两个值是一样的,但它们在内存中是在不同的地方(因为它们是两条不同的鱼)。

这里及以下的比喻中,变量*名*(强调是变量名)用叉子表示,而其值及该值的存在状态是用鱼来代表的,就好比内存就是一大堆鱼,而那个值是其中的一条,注意这里鱼只表示内容,没有名字(你也没听说内存中的某单元被叫某名字吧)。

那么引用是什么呢?引用就是用两把不同的叉子叉到*同一条*鱼上。同样以$a=3为例,已经有一个叫$a的叉子叉到了3这条鱼上,虽然鱼没有名字,但它怎么也算是个东西吧,总还是具体存在的,为了方便称呼,不妨称这条具体的鱼叫"鱼3"(注意这里"鱼3"不是变量名,只是我为了方便叙述人为起的一个名字)。好,前面的废话说了一堆,那到底引用与非引用有什么不同呢?看一下$a=$b这个式子。这个怎么用叉和鱼的关系来解释?下面注意了:
$a = 3     =>    $a  叉在  "鱼3"上
$a = $b   =>    这中间的过程是这样的: 这个式子是传值,于是就先由“鱼场的工作人员(这里当然是PHP解释器了)” *克隆*出一个"鱼3"来,估且叫它 “鱼3一撇”,这个“鱼3一撇”与“鱼3”是完全一样的东西,但却是两个实体,就是说它们在那一大堆鱼中间是不同的(虽然它们看起来完全相同),说白了就是“鱼3一撇”与“鱼3”是两个东西;好,这个克隆完成之后,“鱼场的工作人员”就将$b这个叉子叉到了“鱼3一撇”身上了,于是$b的值也等于3了。但两个3在内存中却不是一个地方。

我想前面说得这么复杂,后面大家应该会有点概念了吧。$a = $b是上面的解释,那么 $a =& $b是个什么概念呢?很简单,就是$b这把叉子直接叉到了“鱼3”身上,根本没有克隆一个什么“鱼3一撇”出来。就是说倒霉的“鱼3”身上被叉了两把叉子,所以一旦“鱼3”被挖了一块的话,两把叉子叉的鱼都少了一块,因为它们叉的本来就是同一条鱼。上面的话用式子表达的话,就是如果$a --(意思是“鱼3”被$a挖了一块),那好,$b也只能为2了,因为$b叉的那条鱼就是被$a挖的那条鱼。

先说这么多吧,要消化还得一阵子。开始我也不理解为什么在PHP中的引用不能被理解为指针,后来明白原理后发现真的不能理解为指针,因为PHP赋值语句与取值表达的二重性使指针这个概念在PHP中并不适用。在PHP中一个变量的名字与值是分开的,但该变量本身却是两者的合二为一。不知道这句话大家能不能理解。就是说$a这个变量在PHP中既是名字又是值,但在底层,这个变量却被分开成名字与值两个部分。在底层,名字就类似于指针,而值只是内存中存这个值的一个区域。而在PHP层面,你将一个值赋给$a,比如$a=3这个式子,这时$a这个变量充当的角色在底层其实是个指针;而当你写echo $a时,$a这个变量在这时却充当了在内存中那个值的角色,因为在C中的你可能得这么写 echo *a,这个星号在这里就是明确指出这个a是一个指针,在JAVA中就更清楚了,除了基本类型变量,其余的全是指针(引用)。但是在PHP中,却并不是这样。也正因为如此,PHP中的引用就不象C或是JAVA中那么清晰,也因此造成了大家在理解上的困难,PHP的方便性在这时反而成了影响理解的一个障碍。

上面讲的是基本原理,其它的如传递引用,返回引用等原理都差不多,只要能理解PHP中变量在内存中是怎么工作的,那么对任何引用问题都是轻而易举就可以掌握并熟练运用的,比如说我现在对这个概念没有一点的问题。

至于说用引用的优劣性,那是另一个问题。在PHP中由于字符串也被作为标量了,因此对字符串用引用意义不大,除非你要明确地修改原值。在提高效率上有帮助的主要是引用数组及大的对象。因为没有文档说在PHP4中数组的传递是用引用方式的,因此估且认为它是传值的,这样的话,复制或是克隆一个数组的开销就比较大了,这时可以用引用。还有对象参数传值也是这样,虽然PHP4中声称对象传值的复制是bit to bit的一一复制,但如果这个对象本身的成员变量太多,这个开销也是很大的。当然PHP5中的一个重大改进,也是开发组声称的great performance improvement,即是将所有的对象传递全部改成引用方式,就是说以后对变量赋一个对象,就不是复制而是传递一个地址了,同时为了解决复制的问题,便在类中多了一个__clone函数来进行bit to bit的复制了。

不管怎么说,要想彻底的搞清楚这个问题,一个关键就在于搞清楚,一个变量在内存中到底是怎么工作的,*变量、变量的名字、变量的值*这三者在内存中到底是个什么关系,当中间一个因素变化时,另外的因素是怎么相应变化的,搞清楚了这个,我保证你对所有的这类问题解决起来都会得心应手的。

我一向的原则就是:尽量弄清楚为什么。只有知道为什么才能做到举一返三,变化无穷尽也;反之,如果会照猫画虎,只是知其然而不知其所以然,那是无论如何都无法变通应用的,这样就只会总有无穷的为什么,却不知道为什么会有这么多的为什么,也永远不可能真正彻底地解决问题。

注:以上用两个星号括起来的表示强调。按道理来说应该画个图就能使大家一目了然,可惜没有时间,以上连打字带组织语言就差不多用了四十分钟。有不明白的再说吧。当然如果有理解更透彻的朋友也请来给大家分享一下吧。

 sports98 回复于:2004-03-26 09:26:36
[quote:64403f0a94]
我想前面说得这么复杂,后面大家应该会有点概念了吧。$a = $b是上面的解释,那么 $a =& $b是个什么概念呢?很简单,就是$b这把叉子直接叉到了“鱼3”身上,根本没有克隆一个什么“鱼3一撇”出来。就是说倒霉的“鱼3”身上被叉了两把叉子,所以一旦“鱼3”被挖了一块的话,两把叉子叉的鱼都少了一块,因为它们叉的本来就是同一条鱼。上面的话用式子表达的话,就是如果$a --(意思是“鱼3”被$a挖了一块),那好,$b也只能为2了,因为$b叉的那条鱼就是被$a挖的那条鱼。 

[/quote:64403f0a94]

上面这段我个人的理解有所不同,我的个人看法是

我想前面说得这么复杂,后面大家应该会有点概念了吧。$a = $b是上面的解释,那么 $a =& $b是个什么概念呢?很简单,就是$b这把叉子直接叉到了“鱼3”身上,根本没有克隆一个什么“鱼3一撇”出来。就是说倒霉的“鱼3”身上被叉了[color=red:64403f0a94]两把叉子[/color:64403f0a94](我个人认为还是一把叉子),所以一旦“鱼3”被挖了一块的话,[color=red:64403f0a94]两把叉子[/color:64403f0a94]叉的鱼都少了一块,因为它们叉的本来就是同一条鱼。上面的话用式子表达的话,就是如果$a --(意思是“鱼3”被$a挖了一块),那好,$b也只能为2了,因为$b叉的那条鱼就是被$a挖的那条鱼。 

[color=blue:64403f0a94]
$a=&$b=3;
我的理解就是
叉子B叉到了 鱼3,但我在说叉子的时候总是不能上书面的(叉子有很多种),于是我就对叉子起了个别名 $a - 我的鱼叉,其实我说[b:64403f0a94]我的鱼叉[/b:64403f0a94]和你们所听到的[b:64403f0a94]鱼叉[/b:64403f0a94]是同一个实体---叉在鱼3身上的那个叉子,注意:我所强调的是鱼三身上只有1把叉子(叉子--别名我的叉子)而不是两把叉子(叉子,我的叉子)。
因此如果你们要求把鱼3的头去了,那么我可能会对你说:“我的鱼叉”上鱼头没了...

与longnetpro的理解不同之处就是 & 等效理解为别名,而不是在思维内重新建立一个同样的概念实体。
[/color:64403f0a94]

 longnetpro 回复于:2004-03-26 11:08:20
这个,怎么说。

其实我的理解应该是叉子才是指针,叉子和鱼在一起时才能被称为一个变量,而鱼就单纯只是变量的值。

又看了一下英文原文关于引用的,发现了真正混乱的原因——其实是它官方文档中说的一些内容用中文理解会弄错,下面我来详解一下。

Chapter 14. References Explained

References in PHP are a means to aclearcase/" target="_blank" >ccess the same variable content by different names. They are not like C pointers, they are symbol table aliases. Note that in PHP, variable name and variable content are different, so the same content can have different names.

首先要搞清楚中间的几个概念到底指什么。这里面提到了几个:
reference,variable name, variable content
直译下来就是 引用,变量名,变量内容。但是,这几个名字到底在PHP中表示什么,可能就会让大家糊涂。这里以 PHP中的代码为例。
先用中文描述:PHP代码中有一个变量 $a,它的值(内容)为3。对于原英文描述中,[b:59c1db9f32]variable name[/b:59c1db9f32]就是[b:59c1db9f32]$a[/b:59c1db9f32],[b:59c1db9f32]variable content[/b:59c1db9f32] 就是 [b:59c1db9f32]3[/b:59c1db9f32],要注意的是,这里的 [b:59c1db9f32]reference[/b:59c1db9f32]指什么?用PHP代码就是 [b:59c1db9f32]&$a[/b:59c1db9f32],注意这里在变量名$a前加了一个&,原文说的引用就是指的这个形式。说它不是指针,因为它与C语言中的 &a 很相似,而&a就是一个指针,原文这样强调&$a不是指针,也是这个意思。那么&$a为什么不是指针呢?官方没有说清楚,它只用了一个UNIX文件系统作类比,这样还是不能解释清楚。上面的叉子和鱼有些接近真实情况了,但还是没有讲得很清晰,刚才又看了一下官方文档,觉得这次应该讲得清楚了。

下面解释为什么 &$a 不是指针。所谓指针其实是指内存某个区域的地址,还是用C语言的 &a 来做类比,这里 a 假设是个整型变量,a本身表示的就是存贮它值的那块内存区的别名,就象楼上说的那样,就是说,在C语言中,a是不存在的,它就是一块内存区的别名,就象我上例说的"鱼3",只是为了称呼方便,你说a或是"鱼3",就是完全指的是那块内存区。但是,在PHP中,情况不同了, &$a 中的 $a 就并不是象C语言中的那个 a 只是代表那块内存区(或是说只是那块内存区的别名)。注意,这里的 a 和 "鱼3"这个别名是地址别名,就是说这个名字就代表那个地址,而不是这个名字指向那个地址。在PHP中却相反,$a 并不是那个地址本身,而是指向那个地址,$a 这时也是一个名字,当然也是某个内存区的别名,但它并不是内容所在的那个内存区,即"鱼3",而是在符号表中的某个区域中,因此$a是符号表中的别名,正好就如官方所说的 they are symbol table aliases,意即$a代表符号表中的某块区域;那这样就清楚了,在底层,$a也还是个变量,也占某个区域,但是在符号表中,这时&$a就可以理解为符号表中那块区域的地址(因为$a表示那块区域,&$a就是那块区域的地址,在这个方面,&$a的确是指针,只不过它指向的并非$a的内容,而是$a这个名字在符号表中的位置——任何一个变量在编译时都要放到符号表中,因此每个名字在符号表中都有一个地址,&$a就是指这个,而不是$a的内容即3所在的那块内存的地址)。因此在这里,在符号表中存贮的内容是$a的值3所在的内存地址,也可以理解为 在底层,$a其实是一个指针,指向变量值所在的地址(符合官方所说的UNIX下文件链接与文件内容的关系),而&$a其实是指针的指针,是二级指针。 

将PHP与C语言对译一下(以下的C语言变量都是指针),PHP其实都是通过a,b来存取内容,而与中间的a_table和b_table都不能直接由PHP代码来控制,因为它们是PHP在编译中产生的符号表中的地址:

$a = 3 就可以理解为  *a_table = 3, a = a_table;或是 **a = 3;

那么 $a = $b = 3 就是 *b_table = 3, b = b_table, *a_table = *b_table 或是 *a_table = 3 (这一步就是克隆), a = a_table,这里a_table与b_table是不一样的,即$a与$b的值所在的内存区是不同的。或是 **a = **b = 3;

而 $b = 3, $a =& $b 就是 *b_table = 3, b = b_table, a_table = b_table, a = a_table,从这个顺序就发现最后a与b间接都链到同一个地方去了。或是 **b = 3,  *a = *b(这一句就是说两个链到一起了);

因此,这样分析下来,楼上说的就错了,应该有两把叉子a和b,不是一个别名,别名其实不存在,因为它们都是第二级的指针。

当然这个东西很大程度上也是只可意会,不可言传,看来很难说得很清楚了,不过我想应该和我说的不会差得太多,而且我每次使用时都与我预期的结果一模一样,从来没有出现过不同,我认为PHP解释器对源代码编译时也一定是这个过程的。

其实还是应该画个图才行。以后再说吧。

 dualface 回复于:2004-03-26 13:58:21
我说的性能损失是Zend Development Enviroment的代码分析功能报告的问题:
[img:2acda4c6d6]http://www.dualface.com/tmp/reference_description.png[/img:2acda4c6d6]


不过刚刚又仔细想了一下,我认为ZDE之所以认为引用性能更差,应该是针对简单类型的
变量,例如整数。看了上面大家的讨论,我简单画了一些图,希望能够对讨论有所帮助。

[img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_01.png[/img:2acda4c6d6]

左边是内存中实际保存的值,也就是变量内容,而右边是符号表,也就是变量名。每个
符号表的项目除了变量名外,还要保存该符号对应的内存地址。从这里看好像PHP的引用
和C的指针是一回事,但实际上是有区别的。

因为php是解释型语言,因此在执行到具体的代码前,没法决定一个变量名对应的实际内
存区域,所以我认为php采用了一种查表法。

[b:2acda4c6d6]1、当遇到变量定义的代码时,php解释器首先在符号表中生成一个新符号项目,然后再
为该变量分配一块内存。[/b:2acda4c6d6]

[img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_02.png[/img:2acda4c6d6]


[b:2acda4c6d6]2、当在表达式中使用该变量时,php解释器从符号表中搜索该变量名,获得实际的内存
地址后进行各种处理。[/b:2acda4c6d6]

[b:2acda4c6d6]3、当调用一个函数时,php解释器首先在符号表中创建一个新项目,并将符号对应的内
存区域复制一块出来。这样在函数中就使用的是新生成那个符号了。[/b:2acda4c6d6]

[img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_03.png[/img:2acda4c6d6]


[b:2acda4c6d6]4、当从函数返回一个值时,php解释器也是首先在符号表中创建新项目、复制内存区域。所以像下面的代码实际上进行了[color=red:2acda4c6d6]四次[/color:2acda4c6d6]内存分配和[color=red:2acda4c6d6]三次[/color:2acda4c6d6]内存区域复制操作:[/b:2acda4c6d6]

[img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_04.png[/img:2acda4c6d6]

[code:1:2acda4c6d6]
function test($b) {
$b = $b + 1;
return $b;
}

$a = 1;
$c = test($a);

// 代码片断 01

[/code:1:2acda4c6d6]

第一次内存分配:
$a = 1;
由于$a没有存在于符号表中,所以php解释器生成了新的符号项目,并分配了内存区域来
保存$a的值:1

[img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_05.png[/img:2acda4c6d6]


第二次内存分配和第一区域拷贝:
test($a)
用$a做参数调用test函数时,php解释器首先生成了$b这个符号项目并为其分配内存,接
着将$a对应的内存区域复制到$b对应的内存区域。这样$b的值就和$a一样了。

[img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_06.png[/img:2acda4c6d6]


第三次内存分配和第二次区域拷贝:
return $b;
函数结束返回值时,php解释器又生成一个新的符号项目(当然这个名字就是php解释器
内部的名字了,从程序员的角度来看就是一个匿名符号)和分配对应的内存区域,然后
把$b对应的内存区域复制到这个匿名符号对应的区域。

[img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_07.png[/img:2acda4c6d6]


第四次内存分配和第三次区域拷贝:
$c = ....
由于$c是个新符号,所以再进行了一次内存分配操作。接着将test函数返回的匿名符号
对应的内存区域复制到$c对应的内存区域。执行完毕后,临时性的匿名符号及其对应的
内存区域就不再使用了。

[img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_08.png[/img:2acda4c6d6]


如果上面的程序是在c语言里面,那么只有两次内存分配操作:

[code:1:2acda4c6d6]
int test(int b)
{
b = b + 1;
return b;
}

int a = 1;
int c = test(a);
[/code:1:2acda4c6d6]

只有在声明int a和int c时才会分配内存,而将a的值传递到test函数时是将a的值复制
到了CPU寄存器中,在test进行运算后的值仍然保存在CPU寄存器中返回。最后复制到c
对应的内存区域。

[b:2acda4c6d6]5、如果函数使用引用作为参数,那么整个过程就有所变化:[/b:2acda4c6d6]

[code:1:2acda4c6d6]
function test(& $b) {
$b = $b + 1;
return $b;
}

$a = 1;
$c = test($a);

// 代码片断 02

[/code:1:2acda4c6d6]

首先,声明$a引起第一次创建新符号和内存分配。然后test($a)时由于是传递引用,所以
php解释器只会生成一个$b符号,但不会再分配新内存,而是让$b指向$a对应的内存区域。
接下来的
$b = $b + 1;
操作实际上就改变了$a对应的内存区域的内容。
最后test函数返回值的时候仍然会创建一个匿名符号并为其分配内存,接下来的操作就和
代码片断01没什么区别了。

[img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_09.png[/img:2acda4c6d6]


[b:2acda4c6d6]6、如果函数返回引用:[/b:2acda4c6d6]

[code:1:2acda4c6d6]
function & test(& $b) {
$b = $b + 1;
return $b;
}

$a = 1;
$c = & test($a);

// 代码片断 03

[/code:1:2acda4c6d6]

在函数返回前的所有过程都和代码片断02相同,不同的地方从函数返回时开始。
首先由于函数是返回引用,所以php解释器创建的匿名符号指向的是$b对应的内存区域。

$c = & test($a);
的结果是$c这个新符号指向匿名符号对应的内存区域。
由于函数又是用引用作为参数,所以代码片断03的最终结果就是只有一块内存被分配出来,
而$a、$b、$c以及那个临时的匿名符号都指向这块内存。

[img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_10.png[/img:2acda4c6d6]



至于性能问题嘛,我去打几盘CS再陪女朋友逛街回来再写 :P

 dualface 回复于:2004-03-26 14:03:14
噢噢,图片还限制了显示宽度和高度说?

 shukebeita 回复于:2004-03-26 15:03:37
我上面关于引用能够提高性能的说法是错误的,感谢dualface。正确的速度测试应该看下面的例子。
[code:1:09372efb5c]
<?php

function getmicrotime()

   list($usec, $sec) = explode(" ",microtime()); 
   return ((float)$usec + (float)$sec); 


//initialize big array1
$bigArray1 = array();

for($ii=1; $ii<20; $ii++) 
{
$bigArray1[$ii]=$ii.'value';
}

//initialize big array2
$bigArray2 = array();
for($ii=1; $ii<20; $ii++) 
{
$bigArray2[$ii]=$ii.'value';
}

//fuction by reference
function passByRef(&$param) 
{
//var_dump($param);
reset($param);
while($kek=key($param)) 
{
$param[$key].='-Processed';
echo($param[$key]) ;
next($param);
}
}

//function by copy
function passByCopy($param) 
{
//var_dump($param);
reset($param);
while($kek=key($param)) 
{

$param[$key].='-Processed';
echo $param[$key] ;
next($param);
}
}

//Call passByRef 50 times
$startTime = getmicrotime();
for($ii=0; $ii<5; $ii++) 
{
passByRef($bigArray1);
}
$endTime = getmicrotime();
$refTime = $endTime-$startTime;

//Call passByCopy 50 times
$startTime = getmicrotime();
for($ii=0; $ii<5; $ii++) 
{
passByCopy($bigArray2);
}
$endTime = getmicrotime();
$copyTime =  $endTime-$startTime;

echo('<hr>Array Test reference time is '.$refTime.'<br>');
echo('Array Test copy time is'.$copyTime.'<br>');
?>
[/code:1:09372efb5c]

 夜猫子 回复于:2004-03-26 16:00:08
对php的引用一直没有花时间仔细考虑过,我是个懒人,不是迫切的需求我一般不去管。
仔细看了longnetpro对于引用的讲解,发觉原来和python里对变量的处理非常相似,也就是“引用计数”的机制,内存里的某块内容被分配给某个变量,那么这块内存的引用计数就是1,如果建立了这个变量的一个引用,那么这块内存的引用计数就为2,以此类推。当引用计数为0的时候,这块内存就会被释放掉。以此来理解shukebeita的代码就是这样

[code:1:66a2e2d037]
/*
粗看感觉$b是$a的引用,$a都被unset了,$b也应该不存在了吧
*/

$a = 1;      // 分配一块内存,内容为1,这块内存的引用计数为1
$b =& $a;    // 建立了$a的引用$b,内存的引用计数变成了2
unset ($a);    // unset $a以后,内存的引用计数减少成为1
echo('b is '.$b.'<br>');  // 由于引用计数还有1,因此这块内存并没有被释放掉,因此可以打印出"b is 1<br>"

/*
下边这一段不如上边一段有迷惑性,呵呵
*/

$a = 1; 
$b =& $a; 
unset ($b); 
echo('a is '.$a.'<br>');
[/code:1:66a2e2d037]

对引用的理解一直就停留在如果希望函数内部的修改能够影响到函数外,那么就用引用传值,希望几位有心得的兄弟能专门说一说在开发类的时候使用引用的一些技巧。

 sports98 回复于:2004-03-26 17:19:07
看了 夜猫的对代码的解释感觉还是赞同的

现在我觉得以上的讨论基本是可以理解引用在PHP中到底是个什么概念了

文章已经收藏~

 dianker 回复于:2004-03-26 18:22:17
谢谢大家了,我插不上嘴,只能说声谢谢....
当然收藏了!

 longnetpro 回复于:2004-03-27 04:24:51
看来还是本人表达不佳,说了这么半天还没有说清楚。不过经过讨论,总算差不多了。

非常感谢dualface的图,他画的图就是我想画的,和我心目中的简直是一模一样。我原来写过一篇英文的关于引用的,说了五六种引用赋值、传递及返回的简单分析,正好对应这位兄弟的几个图,真是一模一样,连匿名符号都一样的,只是我当时好象用的是result来代替。dualface(双面人)老兄看来理解得非常地透彻了。我建议大家仔细看他的图,那是最形象化的说明。我讲了那么多,看来还是没有他的几张图来得清楚。

的确PHP解释时将所有的脚本变量名先存在一个符号表中,然后通过符号表与内存进行映射,同时脚本级的引用对应的就是符号表中映射到同一内容所在的地址——这可能是当时设计ZEND的时候就用的是这个策略,倒不一定是因为PHP是解释语言,比如说PERL就有可能是真正的指针(说错了请指正)。

在一般的概念中(比如在C语言中),非指针的变量名就代表其值的那块内存,引用或是指针就是值或内容的地址。在PHP中,引用就不是这个概念,它并不是值或是内容的地址,而是[b:360bd6edb5]符号表中的变量的名字(不是变量本身)[/b:360bd6edb5]的地址。

还有夜猫子兄说的引用记数,其实就是这么回事,一直忘了说unset的问题,它的确就是引用记数。官方也说得很清楚,unset对引用来说只是unset了名字与内容之间的连接,而并不一定unset其内容本身,就是说让内部指针置空了。如果内容那块内存没有被孤立(还有变量指向它)它就不会被释放,就是说引用记数没有减到0,内容区域就不会被释放。顺便说一句猫兄在代码中有一句说错了,他说
$b =& $a;    // 建立了$a的引用$b,内存的引用计数变成了2
其实并不是建立了$a的引用$b,而是建立了内存的引用$b,因此内存的引用计数变为2,即两个鱼叉叉到了同一条鱼上了。之所以这么说,因为官方有一句话说“建立引用并不是指针赋值,而是将两个变量名同时绑定(bind)到一个内容上”。

关于到性能问题,我个人认为,基于引用的原理,在对简单类型方面,不用引用比较好(二级指针用多了会降低效率),除非你明确要改变源数据内容。

对于字符串,有个概念要搞清楚,在PHP中,字符串和整数一样是被当作简单类型来对待的,因此你$a = $b = 'xxxx'就是将字符串复制两次,就是说内存中应该有两个'xxxx',在符号表中,$a、$b就分别指向这两块内存区的起始点。而在C语言中,却是两个变量指向同一块内存的起始点,因为在C中字符串是char *的,是个指针。这样,如果你对一个[b:360bd6edb5]很大[/b:360bd6edb5]的字符串[b:360bd6edb5]只是读而不是写[/b:360bd6edb5]的话,不妨用引用赋值,这样可以减少一次复制过程。如果要对字符串进行修改,如果只是想改变其中某一个字符的值的话,用$a{$i}这种形式是最快的,因为它就是直接去改源字符串了;而如果需要对字符串进行非定长处理,比如截取或是插入等等,这时PHP编译器则会根据不同的情况来处理了,比如 $s = substr($s, 1),这个式子,因为源和目标都是$s,可能在优化时就不用再复制了,直接把$s的指针往后挪一个(当然要保证引用计数为1的情况下,就是这个字符串只被$s一个变量用),而其它任何情况可能都要复制一遍了,因此对大的字符串来说效率变化比较明显。这个有点类似于JAVA中的String与StringBuffer的区别,String是只读的,如果要处理在内存中就中new出一个新的String出来,因此如果循环处理次数很多的情况下,用String极慢;StringBuffer却与之相反,可以直接改源字符串与C中的char*相似,所以对字符串做大量操作的情况下一般用StringBuffer;在PHP中,字符串同时兼有以上两种特性,在一般情况下是String这种形式的,在引用时,根据当时上下文或是引用记数PHP解释器动态地处理,在用$s{$i}这种形式时,就是StringBuffer这种形式,因此对字符串处理是时候,你也要看上下文及字符串的大小相应地处理。

对于数组,如果比较大,建议用引用,但要注意,用引用就有可能改变原值,如果对原数组只读的话,不妨大胆用引用,没有关系。在PHP中,建立一个数组花的时间最多,因为要散列数组的key和value并存贮(分配内存),特别是数组很大的情况下;又由于PHP中的数组全部都是关联数组,也叫散列数组,就是说在PHP中数组没有下标的概念,而是通过键值去散列查找值的,不是直接的地址对应,因此比较慢。在没有特殊要求或是必需的情况下,最好少用数组。对只读数组,在引用时不妨大胆用引用,在赋值时则要考虑上下文是否用引用赋值,因为一旦这样赋了值,对引用计数会有影响,引用多了,搞不好哪里被别的变量改了,调试起来又云里雾里的。

对象的用法相对比数组简单一点,这个可以直接理解为C++或是JAVA的对象都可以,大多数情况下对象应该用引用,这也是为什么到了PHP5对对象的赋值改成引用赋值了;而相反对对象的复制,也叫克隆反而没有那么常见,因此PHP5中,基类中有一个特殊的__clone()方法,其实就起了PHP4中直接赋值这个功能。因此在PHP4中,如果某个对象的成员变量(注意只是成员变量,包括没有用var显示定义但是在函数中用$this->这样的方式隐式定义的,也就是用var_dump能显示出来的那些成员变量)太多的话,也建议用引用,除非你人为想克隆一个对象出来(这种情况其实相当少见),在PHP5中,我认为有可能会在对象问题上与PHP4的程序发生冲突,因此对PHP4写的程序来说,我认为最好一直都用引用。

 toplee 回复于:2004-03-28 01:01:50
呵呵,有收获,高手过招

 HonestQiao 回复于:2004-03-28 09:25:25
强烈建议使用php的人,好好看看C++,我正在看,我能估计,对我在PHP编程方面的水平有大幅提高

 冰川火云 回复于:2004-03-28 21:41:49
好贴,学习

 flytod 回复于:2004-04-12 00:12:28
dualface ,你画的图看不见了。能再显示一下吗?谢谢。

 dualface 回复于:2004-04-12 01:09:52
不小心删除了,现在恢复了。

 yangshiqi 回复于:2004-08-01 23:39:05
传值和传引用主要是针对变量类型来说的,在php5以前的版本,对于传值来说,非数组(hash)和对象在函数内部进行处理时,会在内存中初始化一块新的空间,并将参数复制存放在堆上,而对于整数和字符串,后台是将该参数的内存地址直接复制使用,也就是说的使用引用.到php5以后,全部使用引用的传递.对于复制一个对象来说,$new_obj = clone $old_obj;来复制,而$new_obj = $old_obj;只是简单的复制内存地址.

原文转自:http://www.ltesting.net