C++ NULL 和 nullptr 区别
一、先一句话总结区别
- NULL :是宏,本质是0 或 (void*)0,不是真正的"空指针",会带来类型问题。
- nullptr :C++11 新增关键字,真正的空指针常量,类型安全,无歧义。
二、详细区别
1. 本质不同
-
NULL
cpp#define NULL 0 // C++ 里通常是这个 // 或 #define NULL (void*)0 // C 语言里是这个它不是指针类型,只是整数 0。
-
nullptr
cppnullptr_t nullptr;是专属空指针类型,只能赋值给指针,不能当整数用。
2. 类型安全性(最重要!)
用 NULL 会出 bug
cpp
void func(int); // 重载 1
void func(char*); // 重载 2
int main() {
func(NULL); // 调用的是 func(int)!不是指针版本!
}
因为 NULL 是 0,编译器优先匹配整数,导致逻辑错误。
用 nullptr 完全安全
cpp
func(nullptr); // 正确调用 func(char*)
nullptr 只能匹配指针类型,不会产生歧义。
3. 隐式转换规则
-
NULL
- 可隐式转成 int
- 可隐式转成 任意指针
→ 容易写错代码、产生隐藏bug
-
nullptr
- 只能转成任意指针类型
- 不能转成整数 (
int a = nullptr;报错)
→ 类型严格、安全
4. 适用标准
NULL:C 和 C++ 都能用nullptr:C++11 及以后 才支持(现代 C++ 强制推荐)
5. 核心区别一览
| 特性 | NULL |
nullptr |
|---|---|---|
| 本质 | 预处理宏,通常定义为 0 或 (void*)0 |
C++11 引入的关键字,类型为 std::nullptr_t |
| 类型 | 整数类型(在 C++ 中) | 特殊的指针空值类型 |
| 类型安全 | ❌ 否,会导致非预期的函数重载匹配 | ✅ 是,只能转为指针或 bool |
| 支持场景 | C/C++ 通用 | 仅 C++11 及以上 |
| 模板支持 | ❌ 差,会丢失指针语义 | ✅ 好,std::nullptr_t 可参与模板推导 |
三、面试最常问的问题
1. 为什么用 nullptr 代替 NULL?
- 类型安全 :
NULL是整数,可能被误认为int,nullptr是真正的空指针 - 重载安全:避免匹配到错误的重载版本
- 代码清晰:明确表达"空指针"的意图
2. nullptr 能被隐式转换成什么?
- 任意类型的空指针:
int* p = nullptr; - 任意成员函数指针
bool类型:if(!p)可工作,转换结果为false- 不能 隐式转为
int:int a = nullptr;会编译错误
3. NULL 和 nullptr 在 if 判断中等价吗?
逻辑上都是假值,但机制不同:
NULL作为0,直接转为falsenullptr通过std::nullptr_t→bool的转换得到false
4. 模板场景下的差异
cpp
template<typename T>
void process(T val) { /*...*/ }
process(NULL); // T 被推导为 int(或 long 等整型)
process(nullptr); // T 被推导为 std::nullptr_t
在泛型代码中,nullptr 能正确保留"空指针"的语义。
5. 实现 nullptr 的关键原理(进阶)
nullptr的类型是std::nullptr_t,本质是一个特殊的类nullptr_t可以隐式转换为任意指针类型,但不能转为整数nullptr_t对象取地址操作是不允许的
四、总结
- NULL 是宏,本质是 0,属于整数类型;nullptr 是 C++11 关键字,是真正的空指针类型。
- NULL 会造成函数重载歧义,nullptr 类型安全无歧义。
- NULL 可隐式转为整数,nullptr 不能。
- 现代 C++ 必须使用 nullptr。
五、建议
- 新代码一律使用
nullptr - 比较指针时:
if (ptr == nullptr)或直接if (!ptr) - 老项目重构可用
-Wzero-as-null-pointer-constant等编译选项找出NULL用法 - 面试时主动提到 C++11 及
std::nullptr_t会是加分项
nullptr 的核心原理
std::nullptr_t 本质上是一个只能隐式转换为任意指针类型,但不能转为整数的类。标准库的实现大概是这样:
cpp
namespace std {
using nullptr_t = decltype(nullptr);
}
但 nullptr 是关键字,所以我们来实现一个功能等价的 my_nullptr。
简化实现
cpp
// 实现一个 nullptr_t 类
class nullptr_t {
public:
// 关键1:可以隐式转换为任意类型的指针
template<typename T>
operator T*() const {
return nullptr; // 实际返回0
}
// 关键2:可以转换为任意成员指针
template<typename C, typename T>
operator T C::*() const {
return nullptr;
}
// 关键3:不能取地址(可选,用于防止误用)
void operator&() const = delete;
// 关键4:支持条件判断
// 通过上述指针转换即可,因为指针可转换为 bool
};
// 创建全局的 nullptr 对象
const nullptr_t my_nullptr = {};
逐步解析设计要点
1. 转换为普通指针
cpp
template<typename T>
operator T*() const { return 0; }
这允许:
cpp
int* p = my_nullptr; // ✅ 推导 T = int,调用 operator int*()
char* q = my_nullptr; // ✅ 推导 T = char,调用 operator char*()
int a = my_nullptr; // ❌ 没有 operator int(),编译错误
2. 转换为成员指针
cpp
template<typename C, typename T>
operator T C::*() const { return 0; }
支持指向成员变量的指针:
cpp
struct MyClass { int x; };
int MyClass::*mp = my_nullptr; // ✅
3. 禁止取地址
cpp
void operator&() const = delete;
真实的 nullptr 不能取地址,加上这个更严谨:
cpp
auto p = &my_nullptr; // ❌ 编译错误
完整测试代码
cpp
#include <iostream>
class nullptr_t {
public:
template<typename T>
operator T*() const { return 0; }
template<typename C, typename T>
operator T C::*() const { return 0; }
void operator&() const = delete;
};
const nullptr_t my_nullptr = {};
// 测试函数重载(这就是 NULL 的问题所在)
void func(int) { std::cout << "int\n"; }
void func(char*) { std::cout << "char*\n"; }
int main() {
// 1. 基本指针赋值
int* p = my_nullptr;
double* q = my_nullptr;
std::cout << (p == nullptr ? "true" : "false") << "\n"; // true
// 2. 不能转为整数
// int a = my_nullptr; // ❌ 编译错误
// 3. 函数重载正确匹配指针版本
func(my_nullptr); // 输出 "char*" ✅
// 4. 成员指针支持
struct Test { int x; };
int Test::*mp = my_nullptr;
// 5. 条件判断
if (!my_nullptr) std::cout << "falsy\n"; // 输出 "falsy"
// 6. 不能取地址
// auto addr = &my_nullptr; // ❌ 编译错误
return 0;
}
还缺什么?(真实实现的额外特性)
我们的简化版已经涵盖了核心功能,但真实的 nullptr 还支持:
-
std::nullptr_t可参与函数重载:cppvoid func(std::nullptr_t) { /* 专门处理 nullptr */ } -
sizeof(std::nullptr_t) == sizeof(void*) -
std::is_scalar_v<std::nullptr_t>为 true -
可以作为非类型模板参数(C++14+)
如果要更完整地模拟,我们可以把这些也加上,但面试中展示前面的核心实现已经足够了。
面试回答建议
回答时可以这样组织:
- 一句话概括 :
nullptr是一个特殊类型的对象,通过模板转换函数只能变为指针 - 画龙点睛 :指出关键就是
template<typename T> operator T*() - 对比升华 :说明这就解决了
NULL被当作整数的问题 - 如果面试官继续追问 :可以提到
std::nullptr_t还能参与重载决议