
在 C++ 的世界里,表示一个“空指针”居然有三种写法:NULL、0 和 nullptr。这像极了茴香豆的“茴”字有几种写法,常常让初学者困惑:到底哪种才是现代 C++ 中正确且优雅的选择呢?
空指针的演进:从模糊到清晰
空指针的核心意义是表示不指向任何有效内存地址。在 C 语言的早期,宏 NULL 通常被定义为 0 或 (void*)0,因此整数常量 0 也被习惯性地用作“空指针常量”。然而,当 C++ 引入了函数重载等更复杂的特性后,这种设计便暴露出了二义性问题。
为了解决这一痛点,C++11 标准引入了关键字 nullptr。它拥有专用的类型 std::nullptr_t,能够明确且类型安全地表示“空指针”,从而彻底解决了 NULL 和 0 在类型推导和重载解析时的模糊性。

nullptr vs. NULL vs. 0:三者的本质区别
-
NULL 的定义模糊性
- 在 C++ 中,
NULL 通常是一个宏,被定义为 0 或 ((void*)0)。这导致在重载函数时可能引发意外的调用。
- 例如,当同时存在
void foo(char*); 和 void foo(int); 时,调用 foo(NULL); 很可能(取决于 NULL 的具体定义)会调用 foo(int),这与我们想传递一个空指针的初衷相悖。
-
nullptr 的明确性
nullptr 是 C++11 引入的专用关键字,其类型就是 std::nullptr_t。它可以隐式转换为任何类型的指针或成员指针。
- 正因类型明确,使用
nullptr 不会产生任何重载解析的歧义。
- 在模板编程中,如果需要判断一个参数是否为空指针字面量,可以使用
std::is_null_pointer 或 decltype。
为了更直观地对比,下表总结了它们的关键特性:

代码实战:看清二义性与解决方案
光说不练假把式,我们通过一段代码来直观感受问题以及 nullptr 如何解决它。
#include <iostream>
#include <type_traits>
void foo(char *);
void foo(int);
int main() {
if (std::is_same<decltype(NULL), decltype(0)>::value)
std::cout << "NULL == 0" << std::endl;
if (std::is_same<decltype(NULL), decltype((void*)0)>::value)
std::cout << "NULL == (void *)0" << std::endl;
if (std::is_same<decltype(NULL), std::nullptr_t>::value)
std::cout << "NULL == nullptr" << std::endl;
foo(0); // 调用 foo(int)
// foo(NULL); // 该行可能无法通过编译,或调用错误的重载
foo(nullptr); // 明确调用 foo(char*)
return 0;
}
void foo(char *) {
std::cout << "foo(char*) is called" << std::endl;
}
void foo(int i) {
std::cout << "foo(int) is called" << std::endl;
}
运行上述代码,输出将会是:
foo(int) is called
foo(char*) is called
代码分析:
- 类型比较:使用
std::is_same 验证了 NULL 的类型可能与 0、(void*)0 或 nullptr 相同,这正说明了 NULL 定义上的不确定性。
- 函数重载:
foo(0) 明确调用了 foo(int)。而被注释掉的 foo(NULL) 则可能因为类型不明确导致编译错误或调用错误的重载。而 foo(nullptr) 则毫无歧义地调用了我们期望的 foo(char*)。
结论很明确:使用 nullptr 能显著提升代码的可读性和可维护性,避免潜在的错误。因此,在现代 C++ 开发中,应优先使用 nullptr 来表示空指针。
深入了解 std::nullptr_t

nullptr 的类型是 std::nullptr_t,它是一个独立的类型,具有以下重要特性:
- 隐式转换:
std::nullptr_t 可以隐式转换为任何对象指针类型和成员指针类型,但不会隐式转换为整型(这与 0 划清了界限),这是其类型安全的基石。
- 布尔上下文:
nullptr 在布尔上下文中(如 if 条件)会转换为 false。
- 大小与地址:
sizeof(std::nullptr_t) 由实现定义,通常等于指针的大小。注意,不能对 nullptr 本身取地址(&nullptr 是非法的)。
- 类型检查:可以使用
<type_traits> 中的 std::is_null_pointer 来检查一个类型是否为 std::nullptr_t。
#include <iostream>
#include <type_traits>
int main() {
std::cout << std::boolalpha;
std::cout << "decltype(nullptr) is nullptr_t: "
<< std::is_same<decltype(nullptr), std::nullptr_t>::value << "\n";
if (nullptr)
std::cout << "nullptr true\n";
else
std::cout << "nullptr false\n";
return 0;
}
现代 C++ 中的最佳实践与常见用法
注意事项与常见误区
补充知识点
C 与 C++ 的差异
C++11 引入了 nullptr,而 C 语言至今仍使用宏 NULL(通常为 ((void*)0) 或 0)。在混合编程时需注意接口边界。
空指针与指针的大小
无论是空指针还是指向类的指针,其指针本身的大小在特定平台上是固定的(32位系统通常4字节,64位系统通常8字节),与所指对象类型无关。
#include <iostream>
int main() {
int* ptr = nullptr;
std::cout << "空指针的大小: " << sizeof(ptr) << " 字节" << std::endl;
return 0;
}
*`void` 指针**
这是一种通用指针,可以指向任何数据类型,但不能直接解引用,必须转换为具体类型后使用。常用于通用函数接口或底层内存操作。
野指针
这与空指针完全不同。野指针指向的是已被释放或无效的内存地址,对其操作会导致未定义行为,是程序崩溃的常见原因。避免野指针是 C++ 内存管理的核心议题之一。

总结
从 0 到 NULL,再到 nullptr,C++ 对空指针的表达经历了一场追求清晰与类型安全的进化。在现代 C++(C++11 及之后)的开发中,nullptr 已成为表示空指针的不二之选。它不仅解决了历史遗留的二义性问题,也让代码意图更加明确。建议开发者养成使用 nullptr 的习惯,同时结合智能指针和 RAII 等技术,从源头上减少裸指针的使用,从而编写出更安全、更健壮的 C++ 代码。
希望这篇关于 C++ 空指针的解析能对你有所帮助。如果你想深入探讨更多 C++ 或 计算机基础 相关的话题,欢迎在云栈社区与我们交流。