C++ NULL 和 nullptr 区别 以及 nullptr 的核心实现

C++ NULL 和 nullptr 区别


一、先一句话总结区别

  • NULL :是宏,本质是0 或 (void*)0,不是真正的"空指针",会带来类型问题。
  • nullptr :C++11 新增关键字,真正的空指针常量,类型安全,无歧义。

二、详细区别

1. 本质不同

  • NULL

    cpp 复制代码
    #define NULL 0          // C++ 里通常是这个
    // 或
    #define NULL (void*)0   // C 语言里是这个

    不是指针类型,只是整数 0。

  • nullptr

    cpp 复制代码
    nullptr_t nullptr;

    专属空指针类型,只能赋值给指针,不能当整数用。


2. 类型安全性(最重要!)

用 NULL 会出 bug
cpp 复制代码
void func(int);    // 重载 1
void func(char*);   // 重载 2

int main() {
    func(NULL);  // 调用的是 func(int)!不是指针版本!
}

因为 NULL0,编译器优先匹配整数,导致逻辑错误。

用 nullptr 完全安全
cpp 复制代码
func(nullptr); // 正确调用 func(char*)

nullptr 只能匹配指针类型,不会产生歧义。


3. 隐式转换规则

  • NULL

    • 可隐式转成 int
    • 可隐式转成 任意指针
      → 容易写错代码、产生隐藏bug
  • nullptr

    • 只能转成任意指针类型
    • 不能转成整数int a = nullptr; 报错)
      → 类型严格、安全

4. 适用标准

  • NULL:C 和 C++ 都能用
  • nullptrC++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 是整数,可能被误认为 intnullptr 是真正的空指针
  • 重载安全:避免匹配到错误的重载版本
  • 代码清晰:明确表达"空指针"的意图

2. nullptr 能被隐式转换成什么?

  • 任意类型的空指针:int* p = nullptr;
  • 任意成员函数指针
  • bool 类型:if(!p) 可工作,转换结果为 false
  • 不能 隐式转为 intint a = nullptr; 会编译错误

3. NULLnullptrif 判断中等价吗?

逻辑上都是假值,但机制不同:

  • NULL 作为 0,直接转为 false
  • nullptr 通过 std::nullptr_tbool 的转换得到 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 对象取地址操作是不允许的

四、总结

  1. NULL 是宏,本质是 0,属于整数类型;nullptr 是 C++11 关键字,是真正的空指针类型。
  2. NULL 会造成函数重载歧义,nullptr 类型安全无歧义。
  3. NULL 可隐式转为整数,nullptr 不能。
  4. 现代 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 还支持:

  1. std::nullptr_t 可参与函数重载

    cpp 复制代码
    void func(std::nullptr_t) { /* 专门处理 nullptr */ }
  2. sizeof(std::nullptr_t) == sizeof(void*)

  3. std::is_scalar_v<std::nullptr_t> 为 true

  4. 可以作为非类型模板参数(C++14+)

如果要更完整地模拟,我们可以把这些也加上,但面试中展示前面的核心实现已经足够了。

面试回答建议

回答时可以这样组织:

  1. 一句话概括nullptr 是一个特殊类型的对象,通过模板转换函数只能变为指针
  2. 画龙点睛 :指出关键就是 template<typename T> operator T*()
  3. 对比升华 :说明这就解决了 NULL 被当作整数的问题
  4. 如果面试官继续追问 :可以提到 std::nullptr_t 还能参与重载决议

相关推荐
旺仔来了2 小时前
不联网的Linux下部署python环境
linux·开发语言·python
JAVA面经实录9172 小时前
MyBatis面试题库
java·mybatis
小江的记录本2 小时前
【JVM虚拟机】垃圾回收GC:垃圾回收算法:标记-清除、标记-复制、标记-整理、分代收集(附《思维导图》+《面试高频考点清单》)
java·jvm·后端·python·算法·安全·面试
之歆2 小时前
Day16_JavaScript 轮播图与事件工程实战(下篇)
服务器·开发语言·前端·javascript·网络·性能优化
小江的记录本3 小时前
【JVM虚拟机】垃圾回收GC:垃圾收集器:G1:Region分区、Mixed GC、回收流程、适用场景(高频)(附《思维导图》+《面试高频考点清单》)
java·jvm·后端·python·spring·spring cloud·面试
Fre丸子_3 小时前
自定义文件夹选取功能
c++
会Tk矩阵群控的小木3 小时前
云控系统在TikTok多账号管理中的核心应用与技术实现
开发语言·php·开源软件·个人开发·tk矩阵
摇滚侠3 小时前
Java 零基础全套教程,反射机制,笔记 187-188
java·开发语言·笔记
Ulyanov3 小时前
用声明式语法重新定义Python桌面UI:QML+PySide6现代开发入门(一)
开发语言·python·算法·ui·系统仿真·雷达电子对抗仿真