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