编译期阶乘必须用 constexpr 函数或模板递归,运行时函数无法在编译期求值;C++11 起 constexpr 函数为首选,但要求所有分支可静态判定,常见错误包括遗漏 constexpr、返回类型不匹配、终止条件不完整等。

编译期阶乘必须用 constexpr 函数或模板递归
直接写 int fact(int n) 运行时函数没法在编译期求值。C++11 起,constexpr 函数是首选方案,但要求所有分支都可被编译器静态判定——比如不能有未定义行为的递归调用、不能用 while(C++14 起才支持)、不能访问非 constexpr 变量。
常见错误是忘记加 constexpr 或返回类型不匹配,比如写成 void 或返回 std::string;还有递归终止条件写成 n == 0 却没处理 n ,导致某些输入下无法常量求值。
-
constexpr函数体必须只含一个return表达式(C++11),C++14 起允许更自由的语句,但依然禁止运行时副作用 - 输入必须是字面量或已知常量表达式,比如
fact(5)可以,int x = 5; fact(x)不行 - 阶乘增长极快,
fact(13)就可能溢出int(32 位),建议用unsigned long long并加静态断言
constexpr 阶乘函数怎么写才安全
最简可行版本(C++14):
constexpr unsigned long long factorial(int n) {
return (n <= 1) ? 1ULL : n * factorial(n - 1);
}但这样没防错。实际使用中建议加上边界检查和类型约束:
立即学习“C++免费学习笔记(深入)”;
- 用
static_assert(n >= 0, "factorial: negative input")捕获负数(C++17 起可在constexpr函数内用) - 对大
n做溢出预判:比如static_assert(n - 返回类型必须足够宽,
int在n=13就溢出,unsigned long long最多撑到n=20
模板特化实现阶乘有什么坑
模板递归是 C++11 之前的主流做法,现在仍有效,但语法更冗长、错误信息更难读。核心是靠部分特化或全特化终止递归:
template<int N>
struct factorial {
static constexpr unsigned long long value = N * factorial<N-1>::value;
};
<p>template<>
struct factorial<0> {
static constexpr unsigned long long value = 1ULL;
};问题在于:模板参数必须是编译期常量,且 N 必须是非负整数。如果误传 factorial<-1>,编译器会无限展开直到报错“template instantiation depth exceeded”,而不是清晰提示。
- 不能用变量做模板实参,比如
int x = 5; factorial<x>::value直接编译失败 - 每个不同
N生成一个新类型,对大范围N可能增加编译时间与符号体积 - C++17 的
inline constexpr变量让factorial<N>::value可直接当常量用,但不如constexpr函数灵活
怎么验证确实是编译期计算
最直接的办法:把它用在需要常量表达式的地方,比如数组长度、enum 初始化、static_assert 条件:
static_assert(factorial(6) == 720, "wrong factorial"); int arr[factorial(4)]; // sizeof(arr) == 24 * sizeof(int)
如果编译通过,说明求值发生在编译期。若换成运行时变量,比如 int n = 4; int arr[factorial(n)],GCC/Clang 会报错 size of array has non-integer type 或类似提示。
注意:调试器里看不到编译期结果,但可以查汇编——factorial(5) 对应的指令里不会出现乘法循环,而是直接加载立即数 120。
真正容易被忽略的是溢出时机:constexpr 计算在编译期触发整数溢出是未定义行为(UB),但多数编译器会静默截断或报错,取决于是否启用 -ftrapv 或类似选项。务必用 static_assert 主动拦截,别依赖编译器警告。


















