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

相关推荐
学渣超几秒前
微服务日志智能诊断系统(七) 日志 RAG 引擎——蒸馏、索引与精准检索
java·后端·agent
掉鱼的猫3 分钟前
代码审查 Agent Harness 实战:AI 自动 Code Review
java·llm·agent
学渣超4 分钟前
微服务日志智能诊断系统(六) Fast Path vs Slow Path——双路径日志诊断策略
java·后端·agent
添砖java_8574 分钟前
网页聊天室测试报告
java·spring boot·spring·maven
hai31524754312 分钟前
九章编译法:DEEPSEEK V3.2汇编编译实例
java·开发语言
lkshop13 分钟前
Next.js 网站 SEO 优化:从 SSR、Sitemap 到结构化数据
开发语言·javascript·ecmascript
ONE_SIX_MIX17 分钟前
lightweight-charts-onesixth v2.8.1 → v3.1.1 更新总览
java·前端·python
SimonKing20 分钟前
一文终结 SpringBoot 国际化!从 0 到 1 完整案例(中/英/日)
java·后端·程序员
ByWalker_20 分钟前
web应用技术—MyBatis入门
java·数据库·mybatis·lombok
在书中成长31 分钟前
HarmonyOS 小游戏《对战五子棋》开发第17篇 - AI的两种评估策略
android·java·javascript