PHP 引用(&)使兩個變量名指向同一個 zval 容器(內存中的值容器)。不同於 C 指針存儲地址,PHP 引用是符號表層面的別名綁定。核心機制是寫時複製 (Copy-on-Write, COW):讀取時共享內存,寫入時才複製。
$a = "ABC";
$b = &$a; // $b 成為 $a 的別名
$b = "EFG";
echo $a; // EFG (兩者同步)
unset($a) 僅刪除符號表條目,不銷毀值容器($b 仍可訪問)。
function add100(&$n) { $n += 100; }
$val = 1;
add100($val); // 調用時不加 &
echo $val; // 101
// 錯誤:add100(1); // 傳值表達式無法引用
// call_user_func_array 特例:需傳引用
call_user_func_array('add100', [&$val]);
function &getStatic() {
static $counter = 0;
return $counter++;
}
$r1 = getStatic(); // 0 (值拷貝)
$r2 = &getStatic(); // 1 (引用綁定)
$r2 = 99;
echo getStatic(); // 100 (靜態變量被修改)
規則:$r = &func() 才建立引用綁定,$r = func() 為值拷貝。
class A { public $v = 1; }
$o1 = new A();
$o2 = $o1; // PHP 5+ 默認對象賦值即引用行為
$o2->v = 2;
echo $o1->v; // 2
// 深拷貝需用 clone
$o3 = clone $o1;
$o3->v = 3;
echo $o1->v; // 2 (不受影響)
PHP 4 時代對象賦值為拷貝,需 &new 顯式引用。PHP 5+ new 自動返回引用,&new 已棄用 (E_STRICT)。
$g = 'global';
function test($useGlobals) {
global $g; // 等價 $g = &$GLOBALS['g'];
if ($useGlobals) {
$GLOBALS['g'] = 'changed via GLOBALS'; // 影響全局
} else {
$g = 'changed local'; // 僅影響局部綁定
}
}
對象方法內 $this 永遠是調用對象的引用,不可重新綁定。
$bigArray = range(1, 100000);
// 情況 A:值傳遞 (COW 生效,無複製)
function readOnly($arr) { return count($arr); }
readOnly($bigArray); // 內存不增
// 情況 B:引用傳遞 (Zend 引擎預判可能寫入,強制複製)
function refParam(&$arr) { return count($arr); }
refParam($bigArray); // 內存瞬間翻倍!
// 情況 C:無引用聲明但函數內修改參數
function modify($arr) { $arr[0] = 0; } // 參數無 &,但內部寫入
modify($bigArray); // 發生 COW 複製,內存翻倍
結論:除非確需修改原變量,不要濫用引用傳參。大數組/字符串傳遞時,值傳遞配合 COW 反而更高效。
| 場景 | 建議 | 理由 |
|---|---|---|
| 函數需修改外部變量 | 引用傳參 func(&$var) | 語義明確,避免返回值賦值 |
| 大數組只讀遍歷 | 值傳遞 func($arr) | 利用 COW 共享內存 |
| 對象方法鏈式調用 | 返回 $this | 對象本身即引用行為 |
| foreach 修改原數組元素 | foreach ($arr as &$v) | 循環後建議 unset($v) 解除引用 |
| 緩存變量手動釋放 | $var = null | 引用計數歸零觸發 GC |
$a = 1;
$b = &$a;
$c = $b;
$c = 2;
echo $a; // 2 ($b 引用 $a,$c 賦值時拷貝值,但 $c=2 時 $c 斷開引用?實測:$c=$b 是值拷貝,$c=2 不影響 $a 輸出 1)
// 正確分析:$b=&$a 綁定,$c=$b 值拷貝(1),$c=2 僅改 $c,$a 仍為 1
echo $a; // 1