PHP 函式參數預設採用「傳值」,但可透過 `&` 符號改為「傳址」。兩者在記憶體行為、效能表現、適用場景上有顯著差異。本文以圖解與實測代碼,徹底釐清兩者機制,助你寫出更高效、可預期的程式碼。
| 特性 | 傳值 | 傳址 |
|---|---|---|
| 語法 | `function foo($x)` | `function foo(&$x)` |
| 記憶體行為 | 複製變數值(Copy-on-Write 機制下未修改時共享) | 直接操作原變數記憶體位址 |
| 函式內修改影響外部 | 否 | 是 |
| 適用資料型態 | 標量、陣列、物件(物件本身為 handle,傳值仍可修改內部屬性) | 需修改呼叫端變數本身(如交換變數、累加器) |
| 效能(大型陣列/字串) | PHP 7+ COW 機制下極佳,未寫入不複製 | 略快於強制複製,但增加耦合風險 |
function byValue($val) {
$val = 'changed inside';
return $val;
}
$original = 'original';
echo byValue($original); // changed inside
echo $original; // original(未變)
function byRef(&$val) {
$val = 'changed inside';
return $val;
}
$original = 'original';
echo byRef($original); // changed inside
echo $original; // changed inside(已被修改)
function swap(&$a, &$b) {
$tmp = $a;
$a = $b;
$b = $tmp;
}
$x = 1; $y = 2;
swap($x, $y);
echo "$x, $y"; // 2, 1
PHP 物件變數實質是「物件識別碼」,傳值時複製的是識別碼,指向同一物件實體:
class User { public $name; }
function rename(User $u) { $u->name = 'New Name'; }
$user = new User();
$user->name = 'Old';
rename($user);
echo $user->name; // New Name(物件內部屬性被修改)
但若在函式內將參數「重新指派」為新物件,傳值不會影響外部,傳址則會:
function replace(User $u) { $u = new User(); $u->name = 'Replaced'; }
function replaceRef(User &$u) { $u = new User(); $u->name = 'ReplacedRef'; }
$u1 = new User(); $u1->name = 'A';
replace($u1);
echo $u1->name; // A(外部仍指向原物件)
$u2 = new User(); $u2->name = 'B';
replaceRef($u2);
echo $u2->name; // ReplacedRef(外部指向被換掉)
許多開發者誤以為「大型陣列傳值會複製記憶體導致效能差」。實際上 PHP 7 引入 COW 機制:
zval 參考計數,不複製底層資料。$big = range(1, 1_000_000); // 約 8MB 陣列
function readOnly($arr) { return count($arr); } // 唯讀
function modify($arr) { $arr[0] = 999; } // 寫入觸發複製
// readOnly($big) 幾乎零開銷
// modify($big) 會複製 8MB 記憶體
掌握傳值/傳址機制,不只是面試題,更是寫出穩健、可維護 PHP 程式碼的關鍵基石。