一篇搞懂 PHP 的引用

周小帥發表於2018-09-23

image

引用變數的解釋

  1. 引用是什麼?
    再PHP中引用意味著用不同的名字訪問同一個變數的內容;
    注意: 在PHP中,變數名和變數內容是不一樣的,因此同樣的內容可以有不同的名字。最接近的比喻是`Unix` 的 `檔名` 和 `檔案本身` -- 變數名是目錄條目,而變數內容則是檔案本身,引用可以被看做是`Unix`檔案系統的 `硬連結` ;
    example1.php

    <?php
    $var = "foo";
    $ref1 =& $var; // 引用 $var 的記憶體空間
    //安裝debug擴充套件,可以使用此方法列印
    xdebug_debug_zval('var');//(refcount=2, is_ref=1)string 'foo' (length=3)
    
    echo $ref1; // >Notice:  Undefined variable: ref1
    echo $var; // >foo
    ?>
  2. 引用不是什麼?
    引用不是指標 , 這意味著一下的結構不會產生預期的效果
    <?php
    function foo(&$var)
    {
    $var = &$GLOBALS["baz"];
    }
    foo($bar);
    ?>

    這將使 foo 函式中的 $var 變數在函式呼叫時和 $bar 繫結在一起,但接著又被重新繫結到了 $GLOBALS["baz"] 上面。不可能透過引用機制將 $bar 在函式呼叫範圍內繫結到別的變數上面,因為在函式 foo 中並沒有變數 $bar(它被表示為 $var,但是 $var 只有變數內容而沒有呼叫符號表中的名字到值的繫結)。

example2.php

<?php
$ref = [1,2,3];
$c = count($ref);
$foo = ['A'];

for($i=0;$i<$c;$i++)
    $foo[] =& $ref[$i];

print_r($foo);
print_r($ref);

$ref = [4,5,6];

print_r($foo);
print_r($ref);
?>
/* 列印結果:
Array
(
    [0] => A
    [1] => 1
    [2] => 2
    [3] => 3
)
Array
(
    [0] => 1
    [1] => 2
    [2] => 3
)
Array
(
    [0] => A
    [1] => 1
    [2] => 2
    [3] => 3
)
Array
(
    [0] => 4
    [1] => 5
    [2] => 6
)
*/

注意example2.php$ref 經歷了 Copy-on-Write 縮寫為 COW 寫時複製

  1. 引用做什麼?
    PHP的引用允許用兩個變數來指向同一個內容
    example3.php
    <?php
    $a =& $b;
    ?>

    這意味著 $a$b 指向了同一個變數。
    注意:
    a> $a$b 在這裡是完全相同的,這並不是 $a 指向了 $b 或者相反,而是 $a$b 指向了同一個地方。
    b> 如果具有引用的陣列被複製,其值不會解除引用。對於陣列傳值給函式也是如此。
    c> 如果對一個未定義的變數進行引用賦值、引用引數傳遞或引用返回,則會自動建立該變數。

example4.php

<?php
// 對未定義的變數使用引用
function foo(&$var) { }
foo($a); // 建立 $a,並用 null 賦值 

$b = array();
foo($b['b']);
var_dump(array_key_exists('b', $b)); // bool(true)

$c = new StdClass;
foo($c->d);
var_dump(property_exists($c, 'd')); // bool(true)
?>

d>同樣的語法可以用在函式中,它返回引用,以及用在 new 運算子中:

example4.php

?>
$bar = &new fooclass();
$fll = &find_var($bar);
?>

自 PHP 5 起,自動返回引用,因此在此使用 =& 已經過時了並且會產生 E_STRICT 級別的訊息。
e> 不用 & 運算子導致物件生成了一個複製。如果在類中用 $this,它將作用於該類當前的例項。
沒有用 & 的賦值將複製這個例項(例如物件)並且 $this 將作用於這個複製上,這並不總是想要的結果。
由於效能和記憶體消耗的問題,通常只想工作在一個例項上面。
儘管可以用 @ 運算子來抑制建構函式中的任何錯誤資訊,例如用 @new,但用 &new 語句時這不起效果。這是 Zend 引擎的一個限制並且會導致一個解析錯誤。
f> 注意:如果在一個函式內部給一個宣告為 global 的變數賦於一個引用,該引用只在函式內部可見。可以透過使用 $GLOBALS 陣列避免這一點。

example5.php

<?php
$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'\n"; // var2 is set to ''
global_references(true);
echo "var2 is set to '$var2'\n"; // var2 is set to 'Example variable'
?>

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

example6.php

<?php
$ref = 0;
$row =& $ref;
foreach (array(1, 2, 3) as $row) {
    // do something
}
echo $ref; // 3  陣列中的最後一個元素
?>
  1. 引用的其他用途:
    引用做的第二件事是用引用傳遞變數。這是透過在函式內建立一個本地變數並且該變數在呼叫範圍內引用了同一個內容來實現的

example7.php

<?php
function foo(&$var)
{
    $var++;
}

$a=5;
foo($a);
?>

將使 $a 變成 6。這是因為在 foo 函式中變數 $var 指向了和 $a 指向的同一個內容。更多詳細解釋見引用傳遞

  1. 引用傳遞
    可以將一個變數透過引用傳遞給函式,這樣該函式就可以修改其引數的值;

example8.php

<?php
function foo(&$var)
{
    $var++;
}

$a=5;
foo($a);
// $a is 6 here
?>

注意:
在函式呼叫時沒有引用符號——只有 函式定義 中有。光是函式定義就足夠使引數透過引用來正確傳遞了。在最近版本的 PHP 中如果把 & 用在 foo(&$a); 中會得到一條警告說"Call-time pass-by-reference"已經過時了。
以下內容可以透過引用傳遞:
1> 變數,例如 foo($a)
2> New 語句,例如 foo(new foobar())
3> 從函式中返回的引用;

example9.php

//以下內容可以透過引用傳遞:
<?php

error_reporting(E_ALL);
function foo(&$var)
{
    $var++;
    echo $var;
}

function &bar()
{
    $a = 5;
    return $a;
}
foo(bar());
?>
  1. 引用返回
    引用返回用在當想用函式找到引用應該被繫結在哪一個變數上面時。不要用返回引用來增加效能,引擎足夠聰明來自己進行最佳化。僅在有合理的技術原因時才返回引用!

example10.php

<?php
class foo {
    public $value = 42;

    public function &getValue() {
        return $this->value;
    }
}

$obj = new foo;
$myValue = &$obj->getValue(); //42
$obj->value = 2;
echo $myValue;                // 列印 $obj->value,  2.
?>

本例中 getValue 函式所返回的物件的屬性將被賦值,而不是複製,就和沒有用引用語法一樣。

注意:
引數傳遞 不同,這裡必須在 兩個地方 都用 & 符號—— 指出返回的是一個引用,而不是通常的一個複製, 同樣也指出 $myValue 是作為引用的繫結,而不是通常的賦值。

如果試圖這樣從函式返回引用:return ($this->value); ,這將 不會起作用因為在試圖返回一個表示式的結果而不是一個引用的變數
只能從函式返回引用變數 ——沒別的方法。
如果程式碼試圖返回一個動態表示式或 new 運算子的結果,自 PHP 4.4.0 和 PHP 5.1.0 起會發出一條 E_NOTICE 錯誤。

6.取消引用
當 unset 一個引用,只是斷開了變數名和變數內容之間的繫結。這並不意味著變數內容被銷燬了。

<?php
$a = 1;
$b =& $a;
unset($a);
?>

不會 unset $b,只是 $a
再拿這個和 Unixunlink 呼叫來類比一下可能有助於理解。

7.引用定位
許多 PHP 的語法結構是透過引用機制實現的,所以上述有關引用繫結的一切也都適用於這些結構。一些結構,例如引用傳遞和返回,已經在上面提到了。

其他的引用結構有:
1> global :當用 global $var 宣告一個變數時實際上建立了一個到全域性變數的引用。

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

這意味著,例如,unset $var 不會 unset 全域性變數。
2> $this :在一個物件的方法中,$this 永遠是呼叫它的物件的引用。

以上就是引用的全部內容;

相關題目:

<?php
 //每次迴圈的結果是什麼?最終程式執行完的結果是什麼?為什麼?
$data = ['a', 'b', 'c'];
foreach ($data as $key => $val){
    $val = &$data[$key];
    var_dump($data);
}
var_dump($data);
?>
本作品採用《CC 協議》,轉載必須註明作者和本文連結
周小帥

相關文章