萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 網路程式設計 >> PHP 程式設計

PHP 變數參考傳遞 vs 值傳遞:記憶體模型與實戰差異深度解析

核心概念對比

特性值傳遞參考傳遞
機制複製值到新記憶體空間傳遞記憶體位址(指標)
修改影響不影響原變數同步修改原變數
記憶體開銷較大(複製資料)極小(僅複製指標)
PHP 預設行為純量、陣列、資源物件(PHP 5+)
語法func($var)func(&$var)

基本範例

<?php
// 值傳遞:函式內修改不影響外部
function byValue($a) {
    $a = $a + 1;
}

$sample = 1;
byValue($sample);
echo $sample; // 1(未變)

// 參考傳遞:函式內修改直接影響外部
function byRef(&$a) {
    $a = $a + 1;
}

$sample = 1;
byRef($sample);
echo $sample; // 2(已變更)
?>

PHP 記憶體模型:Copy-on-Write (COW)

PHP 使用寫入時複製機制優化記憶體:

  • 值傳遞時不立即複製,共享同一記憶體區塊(引用計數 +1)
  • 只有當任一方嘗試修改時,才真正複製資料
  • 因此「值傳遞大陣列」在唯讀情況下極高效
<?php
$bigArray = range(1, 1000000); // 大陣列

function readOnly($arr) {
    // 僅讀取,不修改 → 實際未複製,極快
    return count($arr);
}

function modify($arr) {
    $arr[0] = 999; // 修改 → 觸發 COW,此時才複製整個陣列
    return $arr;
}

// 唯讀:極快,記憶體不增
$start = memory_get_usage();
readOnly($bigArray);
echo "唯讀記憶體增量:" . (memory_get_usage() - $start) . " bytes<br>";

// 修改:複製整個陣列,記憶體激增
$start = memory_get_usage();
modify($bigArray);
echo "修改記憶體增量:" . (memory_get_usage() - $start) . " bytes<br>";
?>

三類資料類型的傳遞行為

1. 純量類型 - 預設值傳遞

<?php
function changeInt($n) { $n = 10; }
function changeIntRef(&$n) { $n = 10; }

$val = 5;
changeInt($val); echo $val; // 5
changeIntRef($val); echo $val; // 10
?>

2. 陣列 - 預設值傳遞(但有 COW 優化)

<?php
function modifyArray($arr) {
    $arr[] = 'new'; // 觸發 COW 複製
}

function modifyArrayRef(&$arr) {
    $arr[] = 'new'; // 直接修改原陣列
}

$arr = [1, 2, 3];
modifyArray($arr);
print_r($arr); // [1,2,3] 未變

modifyArrayRef($arr);
print_r($arr); // [1,2,3,'new'] 已變
?>

3. 物件 - PHP 5+ 預設「物件控制代碼」傳遞(類參考)

⚠️ 關鍵誤區:物件預設不是「參考傳遞」,而是「物件控制代碼的值傳遞」。

<?php
class User {
    public $name;
    public function __construct($name) { $this->name = $name; }
}

function changeProperty(User $user) {
    $user->name = 'Modified'; // 修改同一物件的屬性 ✅
}

function replaceObject(User $user) {
    $user = new User('Replaced'); // 區域變數指向新物件,外部不受影響 ❌
}

function replaceObjectRef(User &$user) {
    $user = new User('Replaced Ref'); // 參考傳遞,外部變數也指向新物件 ✅
}

$u = new User('Original');
changeProperty($u);
echo $u->name; // "Modified" - 同一物件,屬性被改

$u = new User('Original');
replaceObject($u);
echo $u->name; // "Original" - 物件未被替換

$u = new User('Original');
replaceObjectRef($u);
echo $u->name; // "Replaced Ref" - 參考傳遞,變數本身被替換
?>

進階:參考傳遞的實際應用場景

1. 需修改呼叫端變數的函式

<?php
function parseData(string $input, array &$result, &$error = null): bool {
    // 解析邏輯...
    if ($success) {
        $result = $parsedData;
        return true;
    } else {
        $error = "解析失敗:$reason";
        return false;
    }
}

if (parseData($json, $data, $err)) {
    // 使用 $data
} else {
    logError($err);
}
?>

2. 大量資料處理避免複製(謹慎使用)

<?php
function processLargeDataset(array &$data) {
    // 直接操作原陣列,避免 COW 複製開銷
    foreach ($data as &$row) { // 逐行參考修改
        $row['processed'] = true;
    }
    unset($row); // 解除最後一個參考
}
?>

3. 靜態變數參考回傳(PHP 7+)

<?php
class Config {
    private static $instance = null;
    private $settings = [];
    
    public static function &getInstance(): Config {
        if (!self::$instance) {
            self::$instance = new self();
        }
        return self::$instance; // 回傳參考
    }
    
    public function &getSettings(): array {
        return $this->settings; // 允許直接修改設定陣列
    }
}

$config = &Config::getInstance();
$settings = &$config->getSettings();
$settings['debug'] = true; // 直接修改單例內部設定
?>

foreach 參考迭代陷阱

<?php
$arr = [1, 2, 3];

foreach ($arr as &$val) {
    $val *= 2;
}
unset($val); // ⚠️ 必須 unset,否則 $val 仍參考最後一個元素

// 後續錯誤範例
foreach ($arr as $val) { // 無 & 符號
    // $val 現在是值複製,但因上一個參考未解除
    // 實際上會修改 $arr[2] 的值!
    echo $val; // 輸出:2, 4, 4 (最後一個被覆寫)
}
?>

效能建議

  1. 純量/小陣列:預設值傳遞即可,可讀性最高
  2. 大陣列唯讀:值傳遞享受 COW 零複製優勢
  3. 需修改原陣列/物件屬性:傳物件或參考傳遞陣列
  4. 需替換整個變數:必須參考傳遞 &$var
  5. 避免濫用參考:增加認知負擔、難除錯、阻礙引擎優化

總結

PHP 參考傳遞(&$var)讓函式能直接修改呼叫端變數,適用於多返回值、大資料就地修改、物件替換等場景。但現代 PHP 依賴 Copy-on-Write 使值傳遞在唯讀情況下極高效。核心原則:**預設用值傳遞,有明確修改需求時才用參考,物件屬性修改無需參考,物件變數替換才需參考。**

copyright © 萬盛學電腦網 all rights reserved