遞歸函數的核心難點在於「如何在多層調用間傳遞並累積中間結果」。本文深度剖析三種經典實現機制,配合內存模型圖解,幫你徹底理解變量作用域與生命週期在遞歸中的行為。
function test($a = 0, &$result = []) {
$a++;
if ($a < 10) {
$result[] = $a;
test($a, $result); // 傳遞引用,所有層級操作同一個 $result
}
echo $a . ' '; // 觀察輸出順序
return $result;
}
print_r(test());
// 輸出順序:10 9 8 7 6 5 4 3 2 1
// 結果:[1,2,3,4,5,6,7,8,9]
原理解析:
&$result 使參數成為外部變量的別名,所有遞歸層級操作的都是同一塊記憶體echo $a 輸出 10 9 ... 1(而非 1..10),因為遞歸調用在 echo 之前,形成「遞歸深入 → 條件失敗 → 逐層返回執行 echo」的順序function test($a = 0, $result = []) {
global $result; // 聲明使用全局同名變量
$a++;
if ($a < 10) {
$result[] = $a;
test($a, $result);
}
return $result;
}
$result = []; // 全局預定義
print_r(test());
// 結果:[1,2,3,4,5,6,7,8,9]
關鍵點:
global $var 在函數內創建對全局 $var 的引用,而非新建變量&(如 global &$var),行為一致;但 global 本身已是引用語義function test($a = 0) {
static $result = []; // 僅第一次調用初始化
$a++;
if ($a < 10) {
$result[] = $a;
test($a);
}
return $result;
}
print_r(test());
// 結果:[1,2,3,4,5,6,7,8,9]
靜態變量特性演示:
function counter() {
static $count = 0;
echo $count++ . ' ';
}
counter(); counter(); counter(); counter(); counter();
// 輸出:0 1 2 3 4 (而非 0 0 0 0 0)
| 維度 | 引用傳參 | 全局變量 | 靜態變量 |
|---|---|---|---|
| 記憶體共享機制 | 參數別名指向同一地址 | global 聲明指向全局符號表 | 編譯期分配靜態存儲區,首次初始化 |
| 作用域 | 僅限調用鏈 | 全局(污染命名空間) | 函數內部(但生命週期全局) |
| 線程安全 | 相對安全(各調用鏈獨立) | 不安全 | 不安全(同一函數所有調用共享) |
| 代碼清晰度 | 參數列表明確 | 隱式依賴,難追蹤 | 函數簽名乾淨,但隱藏狀態 |
| 推薦場景 | 樹構建、路徑收集、累積計算 | 極少用,僅兼容舊代碼 | 單次腳本執行內的狀態保持、遞歸累積 |
若只需執行邏輯無需收集結果,直接調用即可:
function test($a = 0) {
$a++;
if ($a < 10) {
echo $a . ' '; // 輸出:1 2 3 4 5 6 7 8 9
test($a);
}
}
test();
xdebug_debug_zval() 觀察引用計數static 在編譯期分配、運行期初始化的雙重特性