0! = 1 是阶乘的数学定义,也是递归终止的唯一合法出口;漏掉将导致无限递归、栈溢出或段错误;首行应写 if (n == 0) return 1;还需防范溢出等边界问题。

递归写法:为什么 factorial(0) 必须返回 1
因为阶乘的数学定义就是 0! = 1,不是“没有意义”或“该报错”,而是递归终止的唯一合法出口。漏掉这个特例,factorial(1) 会调用 factorial(0),再调用 factorial(-1)……无限递归直到栈溢出。
常见错误现象:Segmentation fault (core dumped) 或程序卡死,调试时发现调用栈深度异常大。
实操建议:
- 递归函数第一行就该是
if (n == 0) return 1;,别写成n (负数输入应单独处理) - 用
unsigned int或size_t当参数类型,从类型层面排除负数输入可能 - 递归版只适合小数值(
n 左右),再大就会溢出 <code>long long,和递归无关,是整数范围问题
循环写法:for 和 while 在阶乘里没本质区别
两者性能、可读性、安全性几乎一致,选哪个纯看团队习惯或上下文风格。但要注意初始化值和边界条件——循环版最容易错的是把初始结果设成 0 而不是 1。
立即学习“C++免费学习笔记(深入)”;
常见错误现象:永远返回 0,因为 result = 0; for(...) result *= i;,乘法链从零开始就全归零。
实操建议:
- 结果变量必须初始化为
1:long long result = 1; - 循环变量从
2开始即可(1! = 1,乘不乘 1 都一样),避免无谓迭代 - 如果输入是
0或1,循环体一次都不执行,靠初始值1正确返回,这点比递归更“自然”
溢出判断:C++ 没有内置溢出检查,得自己防
无论是递归还是循环,21! 就超出 long long(约 9.2e18),但 C++ 默认不报错,只会静默回绕成负数或小正数,结果完全不可信。
使用场景:如果函数要用于校验输入、生成测试数据或教学演示,不加溢出检测等于给出错误答案。
实操建议:
- 每次乘法前检查:
if (result > LLONG_MAX / i) { /* 溢出 */ },注意除法避免实际溢出 - 用
std::optional<long long></long>或返回std::pair<long long bool></long>表达“算得出来 / 算不出来” - 别依赖
std::numeric_limits的max()做运行时比较——它只是常量,不参与溢出防护逻辑
模板与 constexpr:编译期阶乘真能用,但有硬限制
constexpr 版本在编译期展开,快且零开销,但只适用于字面量输入(比如 factorial()),运行时变量不行;模板递归深度也受编译器限制(通常默认 256 层,factorial() 直接编译失败)。
性能影响:编译时间变长,目标文件体积略增,但运行时确实没函数调用、没循环、没分支。
实操建议:
- 写成变量模板更干净:
template<size_t n> constexpr long long factorial_v = N ? N * factorial_v<n-1> : 1;</n-1></size_t> - 别试图用它算用户输入——
int n; std::cin >> n; auto x = factorial_v<n>;</n>这行通不过编译 - 想兼顾编译期和运行时?得写两个版本,用
if consteval(C++23)或 SFINAE 分流,复杂度陡增,多数场景没必要
最易被忽略的点:阶乘函数从来不只是“算个数”,它暴露的是你对边界、类型、溢出、求值时机的理解。写对 0! 和防住 21! 溢出,比纠结递归还是循环重要得多。











