PHP中引用的詳解(引用計數、寫時拷貝)

《PHP5中文手冊》內容中"引用的解釋"一文的摘要:

1. PHP中引用的特性

PHP中引用意味著用不同的名字訪問同一個變量內容,引用不是C的指針(C語言中的指針里面存儲的是變量的內容,在內存中存放的地址),是變量的另外一個別名或者映射。注意在 PHP 中,變量名和變量內容是不一樣的,因此同樣的內容可以有不同的名字。最接近的比喻是 Unix 的文件名和文件本身――變量名是目錄條目,而變量內容則是文件本身。引用可以被看作是 Unix 文件系統中的緊密連接或者wins的快捷方式。

2.變量的引用

PHP 的引用允許用兩個變量來指向同一個內容。

<?php  
     $a = " ABC " ;  
     $b   =& $a ;  
     echo   $a ; // 這里輸出:ABC  
     echo   $b ; // 這里輸出:ABC  
     $b = " EFG " ;  
     echo   $a ; // 這里$a的值變為EFG 所以輸出EFG  
     echo   $b ; // 這里輸出EFG  
?>

這意味著 $a 和 $b 指向了同一個變量。

注: $a 和$b 在這里是完全相同的,這并不是$a 指向了$b 或者相反,而是$a 和$b 指向了同一個地方。

對象的引用(對象的賦值傳遞)

在PHP5中兩者映射到同一對象,不需要使用引用符。

<?php  
class fooclass{  
    public $att ;   
}  
$a = new fooclass();  
$b = $a;   
$a->att = 1;  
$b->att = 2;  
echo 'a obj:',($a->att),'<br>';  
echo 'b obj:',($b->att);  
/** 
 * 結果: 
 * a obj:2 
   b obj:2 
 */

在PHP5中 對象的賦值是個引用的過程。上列中

$a = new fooclass();  
$b = $a;

其實等效于$b=new fooclass(); $c=&$b;
PHP5中默認就是通過引用來調用對象, 但有時你可能想建立一個對象的副本,并希望原來的對象的改變不影響到副本 . 為了這樣的目的,PHP5定義了一個特殊的方法,稱為__clone。

自 PHP 5 起,new 自動返回引用,因此在此使用 =& 已經過時了并且會產生 E_STRICT 級別的消息。

在php4中,對象的賦值是個拷貝過程,

$a = new foo fooclass(), 實際上$a和new fooclass()的映射到不同對象實例,其中new fooclass產生的是一個匿名的fooclass對象實例 所以需要顯式地使用$a = & new fooclass()來進行引用賦值傳遞。

所以在php4中,為了節省內存空間,$b=new fooclass()一般會改成引用的模式,即 $b=& new fooclass()。

4.函數的引用傳遞(傳址調用)

引用傳遞的定義必須在函數定義中體現,在函數使用中,不要寫成foo(&$a)的樣子,否則會報"Call-time pass-by-reference過時"的警報。

<?php  
    function  foo( & $a ){  
         $a = $a + 100 ;  
    }  
    $b = 1 ;  
    echo   $b ; // 輸出1  
    foo( $b );// 這里$b傳遞給函數的其實是$b的變量內容所處的內存地址,通過在函數里改變$a的值 就可以改變$b的值了  
    echo   " <br> " ;  
    echo   $b ; // 輸出101

 

但是在函數“call_user_func_array”中,若要引用傳參,就得需要 & 符號,如下代碼所示:

function  test( & $b ){  
     $b ++ ;  
}  
$c = 0 ;  
call_user_func_array ( 'test' , array ( & $c ));  
echo   $c ;

5 函數的引用返回

代碼:

<? php  
function   & test()  
{  
     static   $b = 0 ; // 申明一個靜態變量  
     $b = $b + 1 ;  
     echo   $b ;  
     return   $b ;  
}  
$a = test(); // 這條語句會輸出 $b的值 為1  
$a = 5 ;  
$a = test(); // 這條語句會輸出 $b的值 為2  
$a =& test(); // 這條語句會輸出 $b的值 為3  
$a = 5 ;  
$a = test(); // 這條語句會輸出 $b的值 為6  
?>

通過這種方式$a=test();得到的其實不是函數的引用返回,這跟普通的函數調用沒有區別,只是將函數的值賦給$a而已, 而$a做任何改變 都不會影響到函數中的$b。
而通過$a=&test()方式調用函數呢, 他的作用是 將return $b中的 $b變量的內存地址與$a變量的內存地址 指向了同一個地方
即產生了相當于這樣的效果($a=&$b;) 所以改變$a的值 也同時改變了$b的值 所以在執行了

$a=&test();
$a=5;

以后,$b的值變為了5。

注意:

和參數傳遞不同,這里必須在兩個地方都用 & 符號:定義和使用時都需要加&符號。

& test()指出返回的是一個引用,而不是通常的一個拷貝。
$a =& test()同樣也指出 $a 是作為引用的綁定,而不是通常的賦值。

這里是為了讓大家理解函數的引用返回才使用靜態變量的,其實函數的引用返回多用在對象中:

<?php  
/** 
 * This is the way how we  use  pointer to access variable inside the  class . 
 */  
class  talker{  
     private   $data   =   ' Hi ' ;  
     public   function   &  get(){  
         return   $this -> data;  
    }  
    public   function  out(){  
         echo   $this -> data;  
    }    
}  
$aa   =   new  talker();  
$d   =   & $aa -> get();  
$aa -> out();  
$d   =   ' How ' ;  
$aa -> out();  
$d   =   ' Are ' ;  
$aa -> out();  
$d   =   ' You ' ;  
$aa -> out();  
//the output is  " Hi How Are You "

6 global 引用

當用 global $var 聲明一個變量時實際上建立了一個到全局變量$_GLOBAL的引用。具體可以寫成如下語句:

<?php       $var   =&   $GLOBALS [ "var" ];  ?>

把  global $var; 當成是  $var =& $GLOBALS['var']; 的簡寫。從而將其它引用賦給  $var 只改變了本地變量的引用。

如果全局變量賦值給另外另外一個變量$var_test,刪除這個$var_test,也不會 unset 全局變量

<?php  
global $var;  
$var = "Example variable";  
$var_test =  &  $GLOBALS [ 'var' ];  
echo '$var_test is set to ',$var_test,'<br>'; //  
$var_test = 11;  
unset($var_test);  
echo '$GLOBALS[var] is set to ',$GLOBALS ['var'],'<br>';  
/** 
 * 輸出結果: 
$var_test is set to Example variable 
$GLOBALS[var] is set to 11 
 */

如果在一個函數內部給一個聲明為 global 的變量賦于一個引用,該引用只在函數內部可見??梢酝ㄟ^使用 $GLOBALS 數組避免這一點。

$var1 = "Example variable";  
$var2 = "";  
function global_references($use_globals) {  
    global $var1, $var2;  
    if (!$use_globals) {  
        $var2 =& $var1; // visible only inside the function  
    } else {  
        $GLOBALS["var2"] =& $var1; // visible also in global context  
    }  
}  
global_references(false);  
echo "var2 is set to '$var2'", '<br>'; // var2 is set to ''  
global_references(true);  
echo "var2 is set to '$var2'"; // var2 is set to 'Example variable'  
$var2 =& $var1; // visible only inside the function

那么實際上就是改變了$var2的映射關系,之前與$_GLOBAL['var2']的映射就斷開了,再也無法修改函數外部$var2的內容。此特性也同樣作用在用引用傳遞函數參數的例子中。
另外,需要注意在函數中unset($var2)不會真正釋放變量內容,可以看做只是斷開了函數內部$var2的映射而已。

7 . 取消引用 unset與=null

當你 unset 一個引用,只是斷開了變量名和變量內容之間的綁定。這并不意味著變量內容被銷毀了。例如:

<?php  
  
     $a   =   1 ;  
     $b   =&   $a ;  
     unset  ( $a );  
     echo $b; //輸出:1:  
不會 unset $b,只是 $a。

使用unset($a)與$a=null的結果是不一樣的。如果該塊內存只有$a一個映射,那么unset($a)與$a=null等價,該內存的引用計數變為0,被自動回收;如果該塊內存有$a和$b兩個映射,那么unset($a)將導致$a=null且$b不變的情況,而$a=null會導致$a=$b=null的情況。
原因:某變量賦值為null,將導致該變量對應的內存塊的引用計數直接置為0,被自動回收。

8 . $this

在一個對象的方法中,$this 永遠是調用它的對象的引用。
9. PHP引用使用注意事項

很多人誤解Php中的引用跟C當中的指針一樣,事實上并非如此,而且很大差別。C語言中的指針除了在數組傳遞過程中不用顯式申明外,其他都需要使用*進行定義,而php中對于地址的指向(類似指針)功能不是由用戶自己來實現的,是由Zend核心實現的,php中引用采用的是“引用計數、寫時拷貝”的原理,(寫時復制(Copy-on-Write,也縮寫為COW),顧名思義,就是在寫入時才真正復制一份內存進行修改。)

就是除非發生寫操作,指向同一個地址的變量或者對象是不會被拷貝的,比如下面的代碼:

$a = array('a','c'...'n');
$b = $a;

如果程序僅執行到這里,$b和$b是相同的,但是并沒有像C那樣,$a和$b占用不同的內存空間,而是指向了同一塊內存,這就是php和c的差別,并不需要寫成$b=&$a才表示$b指向$a的內存,zend就已經幫你實現了引用,并且zend會非常智能的幫你去判斷什么時候該這樣處理,什么時候不該這樣處理。
如果在后面繼續寫如下代碼,增加一個函數,通過引用的方式傳遞參數,并打印輸出數組大小。

function printArray(&$arr) //引用傳遞  
 {  
    print(count($arr));  
}  
 printArray($a);

上面的代碼中,我們通過引用把$a數組傳入printArray()函數,zend引擎會認為printArray()可能會導致對$a的改變,此時就會自動為$b生產一個$a的數據拷貝,重新申請一塊內存進行存儲。這就是前面提到的“引用計數、寫時拷貝”概念。

直觀的理解:$a將使用自己原始的內存空間,而$b,則會使用新開辟的內存空間,而這個空間將使用$a的原始($a或者$b改變之前)內容空間的內容的拷貝,然后做對應的改變。
如果我們把上面的代碼改成下面這樣:

function printArray($arr)  //值傳遞  
 {  
     print(count($arr));  
 }  
 printArray($a);

上面的代碼直接傳遞$a值到printArray()中,此時并不存在引用傳遞,所以沒有出現寫時拷貝。

測試:

$a = array(1,2,3);  
$b =$a;  
function printArray($arr)  //值傳遞  
{  
    print(count($arr));  
}  
$s = microtime(true);   
for($i=1;$i<10000;$i++){  
    printArray($a);  
 }  
$e = microtime(true);   
echo '=----------------------------';  
echo $e-$s;//0.031868934631348 s
$a = array(1,2,3);  
$b = $a;  
function printArray(&$arr)  //引用傳遞  
{  
    print(count($arr));  
}  
$s = microtime(true);   
for($i=1;$i<10000;$i++){  
    printArray($a);  
 }  
$e = microtime(true);   
echo '=----------------------------';  
echo $e-$s;//0.047234058380127

測試結果

值傳遞:0.031868934631348 s
引用傳遞: 0.047234058380127

結果引用傳遞性能下降:50%左右。
所以不正確使用引用,性能反而下降。

轉自:http://blog.csdn.net/hguisu/article/details/7454801

原創文章,作者:s19930811,如若轉載,請注明出處:http://www.www58058.com/2983

(0)
s19930811s19930811
上一篇 2015-04-10
下一篇 2015-04-10

相關推薦

  • linux中擴展swap分區和移動設備使用掛載以及常用的工具free、df 、du、dd命令

    swap分區 swap分區作用:內存不足時充當系統內存,所以swap盡量放在告訴硬盤上也就是盡量使用磁盤的第一個分區 動手創建swap分區       1、創建分區,將分區類型轉換成swap分區格式           &nbsp…

    Linux干貨 2016-08-29
  • 啟動流程排錯和自建linux系統

    一、grub的配置文件/boot/grub/grub.conf default=0        ###設置默認啟動項0表示第一個 timeout=5        ##設置超時時間,如果超過5s用戶…

    Linux干貨 2016-09-18
  • 五種開源協議的比較(BSD,GPL,LGPL,Apache,BSD)

    BSD開源協議(original BSD license、FreeBSD license、Original BSD license) BSD開源協議是一個給于使用者很大自由的協議。基本上使用者可以”為所欲為”,可以自由的使用,修改源代碼,也可以將修改后的代碼作為開源或者專有軟件再發布。 但”為所欲為”的前提當你發布使用了BSD協議的代碼,或則以BSD協議代碼…

    Linux干貨 2016-01-16
  • 運維的危險命令,用了必死(1)

    Linux命令行佷有用、很高效,也很有趣,但有時候也很危險,尤其是在你不確定你自己在正在做什么時候。這篇文章并不打算引來你對Linux或linux 命令行的憤怒。我們只是想讓你意識到在你運行某些命令時應該三思而后行。(譯注:當然,以下命令通常都是在root權限下才能將愚蠢發揮到無可救藥;在普通用戶身份下,破壞的只是自己的一畝三分地。)

    2017-11-16
  • WEB 常見故障與處理

    一、應用故障 HTTP 502 故障 502 Bad Gateway 故障檢測: 首先定位到前端故障服務器節點,在前端服務器(Telnet)上訪問后端服務端口響應時間。如發現響應時間超時>10s。說明后端應用程序出現故障。需要到后端服務器查看,并查明情況。 PS:HTTP 502 Bad Gateway 故障一般分為以下2種情況: 網絡問題:前端無法連…

    2016-06-03
  • python高階函數與裝飾器

    ##**高階函數**– 函數是python中的一等公民– 函數也是對象,可調用對象– 函數可以作為普通變量、參數、返回值等等– 數學概念y = g(f(x))– 高階函數滿足以下至少一個條件:1.接收一個或多個函數作為參數 2.輸出一個函數 ##**舉例(計數器)**def counter(base…

    Linux干貨 2017-10-23
欧美性久久久久