PHP5中的引用

 以前对于PHP中的引用一直觉得很神秘,与C#里面的不一样,但平时使用的时候这方面用的比较少,或者说很难接触到,所以对PHP里面的引用的理解一直是浅尝辙止,没怎么深入的去研究,正因为如此也一直存在着一种敬而远之的感觉.今天在写一段PHP脚本的时候发现了一个关于使用引用的问题,花了我半天时间才解决了,但是这个问题却让我不开始正视PHP里面的引用,及怎么理解和使用引用.

 

      问题的起因是下面这段代码:

 

 1 class A
 2  {
 3      public $classb;
 4      public $arr = array();
 5      
 6      public function __construct() { $this->classb = new B();}
 7      /**
 8       * @return B
 9       */
10      public function getB(){return $this->classb; }
11      /**
12       *
13       * @return array
14       */
15      public function getArray(){ return $this->arr;}
16      public function __clone() {$this->classb = clone $this->classb;}
17  }
18  class B
19  {
20      public $a;
21      public $b;
22      public function __construct()
23      {
24          $this->= '';
25          $this->= 0;
26      }
27      public function __toString() { return $this->. $this->b; }
28  }
29  
30  $a = new A();
31  $b = $a->getB();
32  $b->= 'abc';
33  $b->= 3
34  $arr=$a->getArray();
35  $arr[0= 'a';
36  $arr[1= 'b';
37 var_dump($a);

 

执行的结果是 $a->classb的值改变了,但是$a->arr的值却没有改变,也就是说明getB返回的一个引用,作用在这上面的一切改变都相当于直接作用在类的成员变量上,而getArray方法返回的只是一个拷贝,也就是说是按值传递的;但是我在getArray前,以及调用的$this->getArray前加上&,显示指出用引用返回,这时候$a-arr的值就被改变了,如果我让getB方法也这样做,效果和没有加&的时候一样.同样的方法声明方式和同样的调用方式,为什么一个默认按引用返回(getB),一个却是按值传递呢?仔细比较这classb与arr这两个变量的类型,发现一个是我自己定义的类,一个是原始的数组类型.会不会是所有基元类型默认按值传递,所有非基元类型(比如说类,接口)都是按引用传递呢.带着这个疑问我又测了好几种基元类型,比如说int,string,bool等,以及重新定义了几个类,使用的方式包括了函数返回值, 参数声明,函数传参数等,测试结果正如我所猜想的那样.

      结合以.NET以及JAVA的类型传递方式我发现PHP里面的类型传递方式完全一样,类类型都是按引用传递,唯一有点区别的是PHP里面把数组当做了值类型的方式来传递,而.net里面的数组是一个引用类型,这也是为什么我对前面那段代码的结果有些疑惑的原因.不知道在什么文章里看到过说是PHP是pass by reference,现在看来这句话也不完全正确,应该所有的类类型是pass by reference所有的值类型pass by value(与C#不同的是字符串在这里应该也算一种值类型,虽说C#里面字符串也默认按值传递).

     回过头来再去查查PHP手册,里面提到客观一句话, 自 PHP 5 起,new 自动返回引用,也就是说用new 实例化的对象获得的全都是引用,什么东西会用new去实例化呢,只能是类类型了.这样看来前面我所说的应该是正确的了.也许是我不够细心吧,总观手册所有章节,没有看到一个地方明确的指出了这个结论,有知道的朋友请告诉小弟一下.

      弄清楚这个问题后,再次面对PHP中面向对象的设计和编程的时候就不用担心什么地方该怎么返回成员了,该不该显示的加&还是不用&,参数声明的时候该不该使用&.长期使用C#编程后总喜欢拿C#来作类比,这次也不例外.我们知道在C#中在参数声明的时候可以使用'ref', 'out'之类的来修饰参数,'out'方式咱先不管,单看这个'ref'.废话不多说了,还是来先看两个例子吧.

 1 class PHPClass
 2 {
 3     public static function TestRef(&$a)
 4     {
 5         $b = $a;
 6         $a = $a + 1;
 7         return $b;
 8     }
 9     public static function TestRef2(&$b)
10     {
11         $b = new B();
12     }
13 }
14 $a = 1;
15 $aa = $a;
16 $result = PHPClass::TestRef($aa); 
17 $b = new B();
18 $bb = $b;
19 PHPClass::TestRef2($bb);

 

 1 public static class CSharpClass
 2 {
 3     public static void TestRef(ref int a)
 4     {
 5         int b = a;
 6         a = a + 1;
 7         return b;
 8     }
 9     public static void TestRef2(ref B b)
10     {
11         b = new B();
12     }
13 }
14 int a = 1;
15 int aa = a;
16 int result = CSharClass.TestRef(ref aa);
17 B b = new B();
18 B bb = b;
19 CSharClass.TestRef(ref bb);

 

这是同一个功能的PHP和C#两种实现,各自己执行完后a和aa,b和bb还等吗?

结果很奇怪,PHP中$a==$aa为false, $b == $bb 为true;C#中a == aa 为false, b == bb也为false.第个使用整型值赋值,传递引用的结果和我想像的基本一致,可是后面一组使用类类型赋值,传递引用的结果,太具有迷惑性了.原来此"引用"非彼"引用"也.虽说前面总结了PHP里面的类类型默认是pass by reference,但是感觉好像PHP里的类类型,所有从同一个对象衍生出来的一系列的引用都是绑在同一个指向真正对象的引用值(想像一下指针吧)上的,或者说是连在同一个脐带上的,一个引用所指向的对象被改变了,所有的引用指向的对象就都被改变了,这个和平时我们做C#程序的时候所理解的大不一样.C#中的引用变量更像是一个独立的和其它引用不相干的值,其中一个被改变成指向新的对象了,其余的也不会改变.因此不难想到,如果把上面的$aa = $a;这句改成$aa = &$a;这样结果中的$a == $aa也就变成true了.

    下面我们来看一个更有迷惑性的代码:

 

 1 function getColor($k)
 2 {
 3   // default return value
 4   $color = 'red';
 5   $value = &$color;
 6 
 7   $arr = array(1: 'red', 2: 'green', 3: 'blue');
 8   foreach($arr as $key=>$value)
 9   {
10     // do something
11     if($k == $key
12     {
13       $color=$value;
14       break;
15     }
16   }
17   return $color;
18 }
19 

 

      这是一个获取颜色的方法,本来想要根据给定的key获取对应的颜色,如果没有的话返回默认值red,可是运行的结果却是找到给定的值的时候,返回的值是正确的,当找不到给定的值的时候,返回的永远是blue却不是我们想要的red,关键的一点就是$value=&$color这一句,它相当于把$value和$color绑到了一个引用值上,后面的foreach循环中$value每改变一次值就相当于$color每改变一次值,所以当没有找到相就的key的值的时候,$value这时的值是数组中最后个的值,也就是blue,因此color的值也就变成了blue而不是默认的red,同时还可以看出中循环中间的$color=$value;这句是多余的,因为$color始终是等于$value的.

 

      看来PHP中的引用不是一句简单的"Pass By Reference"可以概括的了,用的时候更需要特别的小心才不至于出错.我的建议是能不用的时候还是尽量不用吧,否则不知道哪个地方引用了你的变量,又不小心改变了一下值,那你调试起来就巨痛苦了.实在是要用的话在用完之后调用unset,解除引用(又是一个和其它语言中名称相同意思却不一样的一个概念),剪断和所引用的对象之间的脐带关系.

工作五年,长期从事于asp.net方面的编程,业余爱好VC编程,温和、谦虚、自律、自信、善于与人交往沟通
文章转自:http://www.cnblogs.com/bindsang/archive/2008/08/21/1270501.html

没有更多推荐了,返回首页