php變量和對象

一,php變量

php變量分變量名符號和變量內容,變量符號存在符號表中,而變量內容存儲在zval變量容器,具體介紹:php

1,zval結構算法

typedef union _zvalue_value {
    long lval;                  /* long value */
    double dval;                /* double value */
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;              /* hash table value */
    zend_object_value obj;
    zend_ast *ast;
} zvalue_value;

struct _zval_struct {
    zvalue_value value;     /*描述變量的值*/
    zend_uint refcount__gc;
    zend_uchar type;        /*描述變量的類型*/
    zend_uchar is_ref__gc;
};

_zval_strust包含typevalue,另外包含兩個字節的額外信息,is_ref:bool值,標示這個變量是不是屬於引用集合(reference set),php引擎經過is_ref才能把普通變量和引用變量區分開來。refcount:標示指向這個zval變量容器的變量(也稱符號即symbol)個數,用來優化內存使用。數組

符號表:數據結構

全部的變量符號存在一個符號表中,符號有本身的做用域,好比每一個函數或方法域,主腳本域。ide

普通變量類型:函數

當一個變量被賦常量值時,就會生成一個zval變量容器,Example #1性能

<?php

$a = "new string";             // 生成一個新的zval容器

xdebug_debug_zval('a'); //結果:a: (refcount=1, is_ref=0),string 'new string' (length=11)

$c = $b = $a;                  // 把一個變量賦值給另外一變量,增長一個引用計數

xdebug_debug_zval('a'); //結果:a: (refcount=3, is_ref=0),string 'new string' (length=11)

$b = '123';

xdebug_debug_zval('a'); //結果:a: (refcount=2, is_ref=0),string 'new string' (length=11)

unset($c);                   //變量調用了函數 unset()時,」refcount「就會減1

xdebug_debug_zval('a'); //結果:a: (refcount=1, is_ref=0),string 'new string' (length=11)

$d = &$a;

xdebug_debug_zval('a'); //結果:a: (refcount=2, is_ref=1),string 'new string' (length=11)

unset($a);                    //包含類型和值的這個變量容器就會從內存中刪除。

xdebug_debug_zval('a'); //結果:a: no such symbol

?>

定義了變量a,在當前域中生成類型爲string,值位new string的變量容器,此時沒有任何自定義引用,is_ref默認爲0,只有一個變量在使用,refcount默認是1,安裝Xdebug,調用函數 xdebug_debug_zval()顯示"refcount"和"is_ref"的值。測試

b變量=a,同一個變量容器被變量a和變量b關聯,php不會複製已生成的變量容器,refcount變成2,unset變量時,refcount減一、當任何關聯到某個變量容器的變量離開它的做用域(好比:函數執行結束)也會減1,當refcount變成0時就被銷燬。優化

複合類型(Compound Types)ui

array和object符合類型,與普通類型不一樣,符合類型把成員和屬性存在本身的符號表,會有多個zval變量容器

Example #2 

<?php

$a = array( 'meaning' => 'life', 'number' => 42 );    //Creating a array zval

xdebug_debug_zval( 'a' );

/* 結果:
a:(refcount=1, is_ref=0),
array (size=2)
  'meaning' => (refcount=1, is_ref=0),string 'life' (length=4)
  'number' => (refcount=1, is_ref=0),int 42
*/

?>

這三個zval變量容器是: a,meaning和 number。增長和減小」refcount」的規則和上面提到的同樣

Example #3 添加一個已經存在的元素到數組中

<?php

$a = array( 'meaning' => 'life', 'number' => 42 );

$a['life'] = $a['meaning'];   

xdebug_debug_zval( 'a' );

/*結果:
a:
(refcount=1, is_ref=0),
array (size=3)
  'meaning' => (refcount=2, is_ref=0),string 'life' (length=4)
  'number' => (refcount=1, is_ref=0),int 42
  'life' => (refcount=2, is_ref=0),string 'life' (length=4)
*/

?>

原有的數組元素和新添加的數組元素關聯到同一個zval變量容器,輸出顯示兩個值爲'life'的 zval 變量容器,實際上是同一個。 

Example #4 從數組中刪除一個元素

<?php

$a = array( 'meaning' => 'life', 'number' => 42 );

$a['life'] = $a['meaning'];

unset( $a['meaning'], $a['number'] );

xdebug_debug_zval( 'a' );

/*結果:
a:
(refcount=1, is_ref=0),
array (size=1)
  'life' => (refcount=1, is_ref=0),string 'life' (length=4)
*/

?>

刪除數組中的一個元素,就是相似於從做用域中刪除一個變量。刪除後,數組中的這個元素所在的容器的「refcount」值減小,一樣,當「refcount」爲0時,這個變量容器就從內存中被刪除

如今,當咱們添加一個數組自己做爲這個數組的元素時,事情就變得有趣,下個例子將說明這個。例中咱們加入了引用操做符,不然php將生成一個複製。

Example #5 把數組做爲一個元素添加到本身

<?php

$a = array( 'one' );

$a[] =& $a;

xdebug_debug_zval( 'a' );

/*結果:
a:
(refcount=2, is_ref=1),
array (size=2)
  0 => (refcount=1, is_ref=0),string 'one' (length=3)
  '' => (refcount=2, is_ref=1),
    &array<
*/

?>

若是此時unset($a),將刪除這個符號,它指向的變量容器引用計數減1,儘管再也不有某個做用域中的任何符號指向這個結構(就是變量容器),因爲數組元素「1」仍然指向數組自己,因此這個容器不能被清除,php將在腳本執行結束時清除這個數據結構,可是在php清除以前,將耗費很多內存。

若是你要實現分析算法,或者要作其餘像一個子元素指向它的父元素這樣的事情,這種狀況就會常常發生。固然,一樣的狀況也會發生在對象上,實際上對象更有可能出現這種狀況,由於對象老是隱式的被引用。

若是上面的狀況發生僅僅一兩次倒沒什麼,可是若是出現幾千次,甚至幾十萬次的內存泄漏,這顯然是個大問題。

二,引用計數

引用意味着用不一樣的名字訪問同一個變量內容,是變量的另一個別名或者映射。引用能夠被看做是 Unix 文件系統中的緊密鏈接或者wins的快捷方式。

變量的引用

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 
 */  

其實等效於$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()。

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

引用傳遞的定義必須在函數定義中體現,在函數使用中,不要寫成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」中,若要引用傳參,就得須要 & 符號,以下代碼所示:

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

函數的引用返回

<? 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] view plain copy print?

  1. <?php  
  2. /** 
  3.  * This is the way how we  use  pointer to access variable inside the  class . 
  4.  */  
  5. class  talker{  
  6.      private   $data   =   ' Hi ' ;  
  7.      public   function   &  get(){  
  8.          return   $this -> data;  
  9.     }  
  10.     public   function  out(){  
  11.          echo   $this -> data;  
  12.     }    
  13. }  
  14. $aa   =   new  talker();  
  15. $d   =   & $aa -> get();  
  16. $aa -> out();  
  17. $d   =   ' How ' ;  
  18. $aa -> out();  
  19. $d   =   ' Are ' ;  
  20. $aa -> out();  
  21. $d   =   ' You ' ;  
  22. $aa -> out();  
  23. //the output is  " Hi How Are You "  

6 global 引用

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

[php] view plain copy print?

  1. <?php  
  2.      $var   =&   $GLOBALS [ "var" ];  
  3. ?>    

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

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

[php] view plain copy print?

  1. <?php  
  2. global $var;  
  3. $var = "Example variable";  
  4. $var_test =  &  $GLOBALS [ 'var' ];  
  5. echo '$var_test is set to ',$var_test,'<br>'; //  
  6. $var_test = 11;  
  7. unset($var_test);  
  8. echo '$GLOBALS[var] is set to ',$GLOBALS ['var'],'<br>';  
  9. /** 
  10.  * 輸出結果: 
  11. $var_test is set to Example variable 
  12. $GLOBALS[var] is set to 11 
  13.  */  

若是在一個函數內部給一個聲明爲 global 的變量賦於一個引用,該引用只在函數內部可見。能夠經過使用 $GLOBALS 數組避免這一點。

[php] view plain copy print?

  1. $var1 = "Example variable";  
  2. $var2 = "";  
  3. function global_references($use_globals) {  
  4.     global $var1, $var2;  
  5.     if (!$use_globals) {  
  6.         $var2 =& $var1; // visible only inside the function  
  7.     } else {  
  8.         $GLOBALS["var2"] =& $var1; // visible also in global context  
  9.     }  
  10. }  
  11. global_references(false);  
  12. echo "var2 is set to '$var2'", '<br>'; // var2 is set to ''  
  13. global_references(true);  
  14. echo "var2 is set to '$var2'"; // var2 is set to 'Example variable'  

[php] view plain copy print?

  1. $var2 =& $var1; // visible only inside the function  

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


7 . 取消引用 unset與=null

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

[php] view plain copy print?

  1. <?php  
  2.   
  3.      $a   =   1 ;  
  4.      $b   =&   $a ;  
  5.      unset  ( $a );  
  6.      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會很是智能的幫你去判斷何時該這樣處理,何時不應這樣處理。

若是在後面繼續寫以下代碼,增長一個函數,經過引用的方式傳遞參數,並打印輸出數組大小。

[php] view plain copy print?

  1. function printArray(&$arr) //引用傳遞  
  2.  {  
  3.     print(count($arr));  
  4. }  
  5.  printArray($a);  


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

直觀的理解:$a將使用本身原始的內存空間,而$b,則會使用新開闢的內存空間,而這個空間將使用$a的原始($a或者$b改變以前)內容空間的內容的拷貝,而後作對應的改變。

若是咱們把上面的代碼改爲下面這樣:

[php] view plain copy print?

  1. function printArray($arr)  //值傳遞  
  2.  {  
  3.      print(count($arr));  
  4.  }  
  5.  printArray($a);  

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

測試:

[php] view plain copy print?

  1. $a = array(1,2,3);  
  2. $b =$a;  
  3. function printArray($arr)  //值傳遞  
  4. {  
  5.     print(count($arr));  
  6. }  
  7. $s = microtime(true);   
  8. for($i=1;$i<10000;$i++){  
  9.     printArray($a);  
  10.  }  
  11. $e = microtime(true);   
  12. echo '=----------------------------';  
  13. echo $e-$s;//0.031868934631348 s  

 

 

[php] view plain copy print?

  1. $a = array(1,2,3);  
  2. $b = $a;  
  3. function printArray(&$arr)  //引用傳遞  
  4. {  
  5.     print(count($arr));  
  6. }  
  7. $s = microtime(true);   
  8. for($i=1;$i<10000;$i++){  
  9.     printArray($a);  
  10.  }  
  11. $e = microtime(true);   
  12. echo '=----------------------------';  
  13. echo $e-$s;//0.047234058380127  

 

測試結果

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

結果引用傳遞性能降低:50%左右。
因此不正確使用引用,性能反而降低。

舉例:

若是在函數裏面:php的變量是有生命期間的, 一個函數裏面申請的變量的生命只限於這個函數內部,當這個函數執行完畢,它分配的內存都會被銷燬。

對象new後是直接傳遞引用的,$obj=new stdclass;obj是對象標識符的拷貝,都指向同一塊內存,函數執行完畢後對象標識符消除,可是全局依然存在;$obj=&new stdclass;obj是對象的引用別名,函數執行完後對象消除,obj也就跟着消除了。

相關文章
相關標籤/搜索