問題代碼
void prints(char *p) {
if (*p != '\0') prints(++p); // 先遞增指標
if (*p != '\0') printf("%c", *p); // 再列印 -> 跳過第一個字元
}
int main() {
char p[] = "hello";
prints(p); // 輸出 "olle" 少了 'h'
}
錯誤分析
- 呼叫
prints("hello")
++p 使指標指向 'e'
- 遞迴處理
"ello" → "llo" → "lo" → "o" → ""
- 回溯時列印:
'o'、'l'、'l'、'e'
- 原始 'h' 從未被列印,因為第一次遞迴前就已
++p
修正版:先列印再遞迴
void prints(char *p) {
if (*p != '\0') {
printf("%c", *p); // 先列印當前字元
prints(p + 1); // 再遞迴處理下一個 (用 p+1 而非 ++p 避免副作用)
}
}
// 或更簡潔
void prints(char *p) {
if (*p) {
putchar(*p);
prints(p + 1);
}
}
執行流程對比
| 步驟 | 錯誤版 (++p 先行) | 正確版 (列印先行) |
| 1 | p→'h',++p→'e',遞迴 | p→'h',列印'h',遞迴 p+1 |
| 2 | p→'e',++p→'l',遞迴 | p→'e',列印'e',遞迴 p+1 |
| ... | ... | ... |
| 回溯列印 | o, l, l, e | h, e, l, l, o |
進階:尾端遞迴優化 (編譯器支援時)
// 尾端遞迴:遞迴呼叫為最後動作
void prints_tail(char *p, char *end) {
if (p < end) {
putchar(*p);
prints_tail(p + 1, end); // GCC -O2 可優化為迴圈
}
}
// 呼叫
prints_tail(str, str + strlen(str));
遞迴 vs 迴圈效能對比
- 遞迴:程式碼簡潔,但佔用堆疊空間,長字串可能堆疊溢位
- 迴圈:
while (*p) putchar(*p++); 效能最佳,無堆疊風險
- 教學用途:遞迴展示分治思維;生產環境優先迴圈