1. 程式人生 > >php對引用的簡單理解

php對引用的簡單理解

開發 等價 fun enc image 屬性表 bug 內存 不知道

背景

  php語言的高度封裝和五花八門的庫使這門語言很容易上手,而且開發效率比C/C++高出許多。但是也正是由於php封裝度很高,一些在c語言中很簡單的概念,讓php這麽一封裝,就變得難以琢磨。比如引用,在c語言中的概念很簡答, 就是兩個變量名指向同一塊內存。而且引用必須要你手動操作,哪個變量引用的哪塊內存在編寫代碼的時候心裏是一清二楚的。但是在php中,有很多地方使用了隱式的引用,而寫代碼的時候並不知道這是引用。這就很容易造成問題,而且難以發現。就比如下面的代碼我沒有使用引用啊?但是name的值確實被改變了。這種類型的錯誤一旦發生,很可能就應驗了一句話,“一個小bug,一查一下午”,還不一定能查出來。

    技術分享圖片

  輸出:

    技術分享圖片

  我沒有使用引用啊?但是name的值確實被改變了。這種類型的錯誤一旦發生,很可能就應驗了一句話,“一個小bug,一查一下午”,還不一定能查出來。所以這促使我去仔細了解一下php引用的規則,進而在以後的工作中盡量減少這種沒有意義的錯誤。

  下面在梳理引用規則的時候,先不會管php的寫時復制,盡可能從語義的角度來看待php的引用。下面的討論僅適用於php5.

  

php內置類型的引用

  當使用php內置類型時,php的引用規則和C語言沒有區別。例中測試了int, string, array,是預期的結果。只要函數的形參不是引用類型,就不會被莫名其妙的改變。

    技術分享圖片

  輸出:

    技術分享圖片

php對象的引用

  看下面的例子:

    技術分享圖片

  看輸出,坑爹的地方來了:

    技術分享圖片

  不管函數的形參是引用類型還是非引用類型,obj的name成員都被更改了。

  在某篇博客上看到了這樣的解釋:“PHP 5 引進了獨立於變量容器的『對象存儲器』。當一個對象賦值給變量時,變量不再存儲整個對象(屬性表和其他的『類』信息),而是存儲這個對象所在 存儲器的引用 —— 當我們復制一個對象變量時,我們復制的是這個『存儲器的引用』”。把這句話套到例子中就是,$obj現在是一個引用,引用的就是“new Name()”生成的對象存儲器。既然知道了$obj只是個引用,那麽解釋上面例子為什麽會這樣輸出就很容易了:

  對於fun函數,當實參賦值給形參的時候,相當於 $arg = $obj。當我們復制一個變量對象時,復制的是這個對象存儲器的引用。那麽現在的情況就是,$arg也引用剛才"new Name()"生成的對象存儲器,這個對象存儲器的引用計數是2。所以在fun函數中,我們操作$arg->name=“fun”,本質上就是讓這個對象存儲器中的name屬性編程“fun”。當fun函數返回時,$arg被銷毀,此對象存儲器引用計數減一。但是$obj仍然引用著這個對象存儲器,所以我們 echo $obj->name的時候,輸出此對象存儲器的name屬性,即“fun”。

  對於funReference, 當實參賦值給形參時,相當於$arg = &$obj。$arg作為$obj的引用,而$obj作為對象存儲器的引用。在解引用的時候,可以簡單的認為,$arg->name="funReference"就是在直接操作對象存儲器。所以funReference和fun造成了同樣額結果。

  但是是不是$arg = $obj 和 $arg = &$obj 就完全等價了?事實證明,不是,舉個例子:

    技術分享圖片
  輸出:

    技術分享圖片

  可以看出,$obj2=$obj1, $obj2復制的是對象存儲器的引用,所以後續就算是$obj指向了其他的結構,$obj2仍然引用的是對象存儲器。但是$obj3 = &obj1, $obj3是$obj1的引用,按照C語言的引用規則,當$obj1的指向發生改變時,$obj3也應該指向也會發生改變,使之和$obj1指向相同的內存。php也確實這麽去實現了這個語義。所以當obj後來指向“abc”時,$obj3也指向了"abc"。php的引用指向在初始化後,還可以隨意改變,真實讓人腦殼疼。

  另外一個問題,php的對象沒法被賦值嗎?因為不管怎麽弄,我拿到的都只是相當於對象存儲器的引用。php開發者也想到這個問題了,我在網上找到了下面的兩種辦法:

    1. 給類實現一個__clone()方法。然後賦值的時候這樣:$obj2 = clone $obj1;

    2. 使用序列化函數。$obj2 = unserialize(serialize($ojb1));

global,$GLOBALS, 引用

  先給個定義:

    $GLOBALS[‘var_name‘], 是全局變量$var本身

    global $val是全局變量的同名引用

  這兩句話讓人感覺有點雲裏霧裏,舉個例子就清楚了:

    技術分享圖片

  輸出:

    技術分享圖片

  可以看到,var1並沒有被改變,而var2則被改變了。

  先解釋var1, 開頭說了,global var 是 全局變量 var 的同名引用。也就是說,在fun()內部,var1只是全局變量var1的引用而已。看看fun()中做了什麽?$var1=&$str, 讓var1重新引用局部變量str。這個操作只是讓fun內部的同名引用var1所引用的對象變了,和全局的var1一點關系都沒有,所以var1不會改變。

  $GLOBALS的官方定義是,由所有已定義的全局變量所組成。$GLOBALS["var_name"]就是全局變量var_name本身。套到例子中,$GLOBALS["var2"]也就是全局變量var2本身,讓全局變量引用fun中的局部變量str,所以var2的值就發生了改變。

  這兩個概念一定要區分清楚,否則在平時編碼中,就會發生想改變的全局變量沒有被改變,但是不想被改變的全局變量反而被意外改變。而且這種bug還真不好找。

  

  

php對引用的簡單理解