更新时间: 2026-08-27
如果你是从 C 转过来的,大概对 NULL 很熟:if (ptr == NULL)、ptr = NULL。到了 C++ 里,你会看到代码里写 nullptr。这篇就把这个"换了名字的空指针"讲清楚——它不只是改个名,而是修了一个 C 时代就存在的坑。
本文要回答:NULL 到底有什么问题?nullptr 强在哪?以及 0、NULL、nullptr 三者是什么关系?
一、NULL 的历史问题
C 里 NULL 是宏,标准允许它定义为 0 或 (void*)0。C++ 里为了兼容,通常定义为 0 或 0L——反正就是整数 0。问题就出在这:NULL 是个整数,不是指针。
看这个例子:
void f(int);
void f(const char*);
f(NULL); // 调用的是 f(int) 还是 f(const char*)?在典型实现里,NULL 就是整数 0,所以重载决议把 f(NULL) 解析成 f(int)——跟调用者的直觉完全相反!写代码的人心里想的是"传空指针",编译器却调了整数版本。这就是 NULL 的类型缺陷:它没有指针类型,无法参与指针相关的重载决议。
nullptr 是 C++11 引入的关键字,专门表示"空指针",类型是 std::nullptr_t:
void f(int);
void f(const char*);
f(nullptr); // 明确调用 f(const char*),零歧义nullptr 能隐式转换成任何指针类型(int*、char*、void*、成员指针……),但不能转换成整数。所以上面重载场景下,编译器只能选指针版本。
二、nullptr 与 0 / NULL 的关系
用一个图说清楚:
@startuml
left to right direction
skinparam nodeFontSize 13
skinparam backgroundColor #FFFFFF
rectangle "0" as zero #E8F1FF {
node "类型:int" as z1 #DCE9FF
node "可转指针(历史遗留)" as z2 #FFF3D6
}
rectangle "NULL" as null #E8F1FF {
node "类型:int / 0L(宏)" as n1 #DCE9FF
node "本质是 0,不是指针" as n2 #FFF3D6
}
rectangle "nullptr" as np #D9FFE2 {
node "类型:std::nullptr_t" as p1 #D9FFE2
node "只能转指针,不能转整数" as p2 #D9FFE2
}
@enduml| 写法 | 类型 | 能否当指针用 | 能否当整数用 | 重载决议 |
|---|---|---|---|---|
0 | int | 能(隐式转空指针) | 能 | 可能选错 |
NULL | int 或 0L | 能(隐式转空指针) | 能 | 可能选错 |
nullptr | std::nullptr_t | 能(隐式转任意指针) | 不能 | 永远选指针版本 |
NULL 的历史地位:它让 C/C++ 能用一种写法表达"空指针",代价是类型上是整数、语义上是指针,两边不讨好。nullptr 把"空指针"从语义变成了类型——这就是现代 C++ 的典型风格:让类型表达意图。
三、空指针的判定
三种写法在 if 判断里行为完全一致,都判"指针为空":
int* p1 = nullptr;
int* p2 = NULL; // 兼容老代码
int* p3 = 0; // 也能编译
if (p1) { /* 非空 */ }
if (!p1) { /* 为空 */ }
if (p1 == nullptr) { /* 为空 */ }新代码统一用 nullptr:赋值、比较、初始化都写它。if (p) / if (!p) 的隐式 bool 转换依然是标准姿势,不用写成 if (p != nullptr)(后者也没错,只是啰嗦)。
四、C 对照
| 场景 | C | C++ |
|---|---|---|
| 定义 | #define NULL ((void*)0) 或 0 | nullptr 关键字 |
| 类型 | 宏,无类型 | std::nullptr_t |
| 赋给指针 | p = NULL; | p = nullptr; |
| 判空 | if (p == NULL) | if (!p) / if (p == nullptr) |
| 重载/泛型 | 无此场景 | 类型安全,参与决议 |
C 没有重载和模板,NULL 的类型缺陷暴露不出来;C++ 有了重载和模板,NULL 的整数身份就处处添堵。所以 C++11 补上了 nullptr。
五、nullptr 在模板里的价值
nullptr 的另一个用武之地是模板/泛型代码。写模板时经常要表示"空值",如果写 0,类型推导可能推导成 int;写 nullptr,推导出 std::nullptr_t,能干净地转成需要的指针类型:
#include <memory>
template <typename T>
void reset_ptr(T& p) {
p = nullptr; // 不管 p 是 T* 还是智能指针,都能赋值
}智能指针也有 nullptr 相关重载(reset()、判空),C++11 起它们和 nullptr 无缝配合。这也解释了为什么现代代码里几乎见不到 NULL 了——它是被 nullptr 取代的旧写法。
六、与本站主线衔接
- 43-47 篇智能指针:
make_unique/make_shared失败时返回空智能指针,判空if (p)和nullptr语义一致 crash/空指针解引用相关章节:很多崩溃源于"把 NULL 当指针用"或"忘记判空",nullptr让空指针检查更明确- 51 篇
std::optional:表示"可能没有值"的另一套方案,与空指针思路互补
七、一句话总结
nullptr 是 C++11 起表达空指针的关键字,类型为 std::nullptr_t,只转指针不转整数,修掉了 NULL(本质是 0)在重载决议中"被当成整数"的历史缺陷——新代码一律写 nullptr,NULL 留给读老代码时认识。