48、C++11新特性(nullptr与constexpr):空指针常量、编译期常量
说实话,C++11之前,处理空指针和常量表达式这两件事,我一直觉得有点「别扭」。
先说说空指针。以前我们用 NULL 或者直接写 0。但 NULL 在 C++ 里其实是个宏,通常被定义为 0 或者 (void*)0。这就带来了一个很尴尬的问题——函数重载的时候,编译器会搞混。
核心要点:nullptr 解决了空指针的类型安全问题,constexpr 让编译期计算变得优雅可控。
nullptr:终于有了真正的空指针类型
我记得刚入行那会儿,写过这样的代码:
void foo(int) {
cout << "调用了 int 版本" << endl;
}
void foo(char*) {
cout << "调用了指针版本" << endl;
}
int main() {
foo(NULL); // 猜猜调用了哪个?
foo(nullptr); // 这个呢?
}
猜到了吗?foo(NULL) 实际上调用了 foo(int)。因为 NULL 就是 0,编译器优先匹配了整型版本。这在我早期的一个项目里引发过 bug——我明明想传空指针,结果走进了整型分支。
nullptr 的出现就是为了终结这种混乱。它的类型是 std::nullptr_t,可以隐式转换为任何指针类型,但绝不会被当作整型。
我个人习惯,从 C++11 开始,所有空指针一律用 nullptr,不再碰 NULL。你想想看,代码里写 nullptr,读起来也更直观——「哦,这是个空指针」。而 NULL 总让人多犹豫半秒。
小技巧:如果你在模板代码里需要判断一个指针是否为空,用 nullptr 配合 if constexpr(C++17)效果更佳。
constexpr:把计算搬到编译期
再说 constexpr。这个关键字我一开始觉得有点多余——不是有 const 吗?
后来我明白了。const 只是承诺「这个值不会变」,但它的值可能是在运行时才确定的。而 constexpr 要求「这个值必须在编译期就能算出来」。
举个例子:
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int val = factorial(5); // 编译期就算好了
int arr[val]; // 可以用作数组大小
}
这段代码里,factorial(5) 在编译期就计算出了 120。val 是编译期常量,所以可以用来定义数组大小。这在 C++98 里是做不到的——那时候数组大小必须是字面量。
我在项目中遇到过一种场景:需要预计算一组查表数据,比如正弦值。用 constexpr 函数,可以在编译期生成整个数组,运行时直接查表,性能提升很明显。
注意:constexpr 函数在 C++11 中限制较多——函数体只能有一条 return 语句。C++14 放宽了这个限制,可以包含循环和局部变量。C++17 和 C++20 又进一步扩展了能力。
constexpr 与 nullptr 的配合
这两个特性看似独立,其实在泛型编程和模板元编程里经常一起出现。比如:
template <typename T>
constexpr T* get_null() {
return nullptr;
}
int main() {
int* p = get_null<int>();
double* q = get_null<double>();
}
这里 nullptr 提供了类型安全的空指针,constexpr 保证了函数在编译期求值。两者结合,写出来的代码既安全又高效。
避坑指南
我曾经踩过一个坑:在 constexpr 函数里调用了非 constexpr 函数。编译器直接报错,说「这个函数不能用于常量表达式」。嗯,当时我一脸懵。
后来养成了习惯:写 constexpr 函数时,确保它依赖的所有函数也都是 constexpr 的。否则,这个函数在运行时可以调用,但在编译期求值时就会失败。
另外,nullptr 虽然好,但别滥用。比如:
int* p = nullptr;
delete p; // 删除空指针是安全的,但没必要
删除空指针是合法的,但如果你每次都要检查指针是否为空,那说明设计上可能有问题。我个人建议:用智能指针代替裸指针,让生命周期管理更清晰。
知识体系图
总结一下
nullptr 和 constexpr 是 C++11 里两个看似简单、实则影响深远的特性。一个解决了空指针的类型安全问题,一个让编译期计算变得可控且可读。
我个人觉得,这两个特性是 C++ 从「带类的 C」走向「现代 C++」的重要标志。如果你还在用 NULL 和 const 硬扛,不妨试试 nullptr 和 constexpr——写起来更舒服,跑起来更放心。
一句话记住:空指针用 nullptr,编译期常量用 constexpr。别问为什么,问就是 C++11 的良心。
公众号:蓝海资料掘金营,微信deep3321