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)!不是指针版本!
}

因为 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,直接转为 false
  • nullptr 通过 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 对象取地址操作是不允许的

四、总结

  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 还能参与重载决议

相关推荐
CoderYanger6 小时前
Java EE 进阶:8.MyBatis 操作数据库(入门)(第二弹)
java·数据库·spring boot·spring·面试·java-ee·mybatis
CQU_JIAKE9 小时前
11.7[高体】
java·后端·spring
apcipot_rain9 小时前
C++转Java日记(04)
java·开发语言·c++
特立独行的猫A10 小时前
我给女儿写了个打字练习:一边敲键盘,一边把小学英语单词记牢
java·后端·python
云和数据.ChenGuang11 小时前
LangGraph 异步 Node 的设计与写法实践
开发语言·人工智能·python·机器学习·php
憧憬成为java架构高手的小白12 小时前
LangGraph4j 实战(下)
java
WL学习笔记12 小时前
vector模拟实现
c++
天诰12 小时前
SA-K-11 检索会消失吗与四个未解问题
java·服务器·人工智能·python·机器学习·ai编程
蜗牛一步步往上爬14 小时前
第十六章 C++语言在深度学习中的应用
开发语言·c++·深度学习
这个昵称不容易16 小时前
SpringBoot「衣依」服装销售平台:144个Java文件180个接口,从挑衣服到收货评价整条链路都在
java·springboot·毕设