PHP 的參考(Reference)讓多個變數指向同一份資料(zval 容器),是效能優化與複雜資料結構操作的利器,但也因「寫時複製」機制與版本差異產生許多陷阱。本文系統整理變數參考、函式參考傳遞/回傳、物件參考、取消參考、全域參考等核心概念,並附上經典面試題解析。
$a = & $b; // $a 與 $b 指向同一 zval 容器,非指標、非「$a 指向 $b」
unset($a) 僅斷開 `$a` 與 zval 的綁定,不銷毀資料(`$b` 仍可存取)。function add100(&$n) { $n += 100; }
$b = 1;
add100($b); // $b 變為 101
&,呼叫端**不需**加 &(PHP 5.4+ 呼叫端加 & 會報 Deprecated)。add100(1) 會報錯,必須是變數。function &getCounter() {
static $c = 0;
$c++;
return $c;
}
$v1 = getCounter(); // 1 (值拷貝)
$v1 = 5;
$v2 = getCounter(); // 2 ($v1 修改不影響靜態變數)
$v3 = &getCounter(); // 3 (參考回傳,綁定靜態變數 $c)
$v3 = 5;
$v4 = getCounter(); // 6 ($c 已被改為 5,再 +1 = 6)
&**。class A { public $v = 'ABC'; }
$b = new A;
$c = $b; // PHP 5/7/8:$c 與 $b 指向同一物件實例(隱性參考)
$c->v = 'DEF';
echo $b->v; // DEF
$c = &$b。clone:$copy = clone $obj; 並可實作 __clone() 深層複製。& 參考傳遞。| 場景 | 建議 |
|---|---|
| 大型陣列/物件唯讀傳遞 | **不用參考**,靠寫時複製自動優化 |
| 需在函式內修改原陣列/物件 | 函式參數加 & 參考傳遞 |
| foreach 修改原陣列值 | foreach ($arr as &$v) { ... } unset($v); |
| 靜態快取/單例模式 | 靜態屬性 + 參考回傳(或直接存取靜態屬性) |
| 避免物件複製 | PHP 5+ 預設即為參考行為,無需額外 & |
global $var 等同 $var = & $GLOBALS['var'],建立到全域變數的參考。global $var; $var = &$other; 僅改變區域參考,不影響全域。需改全域請用 $GLOBALS['var'] = &$other;。$this 永遠是呼叫物件的參考,不可重新指派。$a = 1; $b = &$a; unset($a); // $b 仍為 1,$a 被銷毀
unset 僅移除符號表項目,不影響 zval 參考計數(refcount)。當 refcount 歸零時才真正釋放記憶體。
$a = 'ABC'; // zval(refcount=1, is_ref=0)
$b = $a; // zval(refcount=2, is_ref=0) 共享同一記憶體
$a = 'EFG'; // 觸發寫時複製:為 $a 分配新 zval,原 zval 留給 $b
$b = &$a 會將 is_ref=1,之後修改任一變數**不會**觸發複製,直接修改共享 zval。$a = 1;
$x = &$a; // $x 參考 $a
$b = $a++; // 後置遞增:$b = 1, $a = 2
// 結果:$b = 1, $x = 2($x 參考 $a,故同步變化)
關鍵點:$a++ 先取值再遞增,但因 $x 參考 $a,遞增動作直接作用於共享 zval,故 $x 同步看到新值 2。
PHP 參考是「符號表別名」而非 C 指標。核心原則:
&。&。clone。unset($v)。refcount/is_ref 與 unset 行為,能避免記憶體洩漏與邏輯錯誤。