尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

C++ 中 nullptr 与 NULL 的区别:为什么现代 C++ 必须使用 nullptr

C++ 中 nullptr 与 NULL 的区别:为什么现代 C++ 必须使用 nullptr
📅 发布时间:2026/7/25 12:22:44

C++ 中 nullptr 与 NULL 的区别:为什么现代 C++ 必须使用 nullptr


一、引言:一个看似微小的改变


在 C++11 之前,程序员使用NULL宏来表示空指针。C++11 引入了nullptr关键字作为空指针的专用字面量。这个变化看似只是语法糖,实际上解决了NULL在 C++ 中由来已久的类型安全问题和函数重载歧义问题。


许多开发者仍然习惯性地使用NULL,但在现代 C++ 代码中,nullptr是唯一正确的选择。理解为什么需要这个新关键字,对于写出类型安全、行为可预测的 C++ 代码至关重要。


二、核心区别速览


| 维度 | NULL | nullptr |

|------|------|---------|

| 本质 |宏定义(通常定义为0或(void*)0) |关键字,std::nullptr_t类型的字面量 |

| 类型 |整数类型(C++ 中) |std::nullptr_t(专门的空指针类型) |

| 与整数比较 | 等于 0(隐式转换) | 等于 0(可比较,但不是整数) |

| 函数重载 |歧义:可能匹配int版本 |精确:始终匹配指针版本 |

| 类型安全 |差:可隐式转换为 int |好:只能转换为指针或 bool |

| 模板推导 | 推导为int| 推导为std::nullptr_t|

| 可读性 | 不明确(是整数还是指针?) | 明确(一眼看出是指针相关) |


三、NULL 的本质与问题


3.1 NULL 是什么


// C++ 标准库中 NULL 的典型定义: #define NULL 0 // C++ 中的常见定义 // 或 #define NULL ((void*)0) // C 中的常见定义(C++ 不允许 void* 隐式转换到其他指针) #include <iostream> int main() { // NULL 实际上是整数 0 std::cout << "NULL value: " << NULL << std::endl; // 0 std::cout << "sizeof(NULL): " << sizeof(NULL) << std::endl; // 4 (sizeof(int)) // 这是一个普通的整数,可以参与算术运算 int x = NULL + 5; // 完全合法!x = 5 std::cout << "NULL + 5: " << x << std::endl; }


3.2 问题一:类型不安全


#include <iostream> void processPointer(int* ptr) { std::cout << "Processing pointer" << std::endl; } void processInteger(int value) { std::cout << "Processing integer: " << value << std::endl; } int main() { // 使用 NULL:意图是传空指针 processPointer(NULL); // 输出: Processing pointer(多数编译器如此) processInteger(NULL); // 输出: Processing integer: 0 // 问题:NULL 是整数 0,两者都可以匹配 // 编译器可能选择任意一个(取决于实现) // 调用者意图不明确 // 使用 nullptr:意图明确 processPointer(nullptr); // 输出: Processing pointer(明确) // processInteger(nullptr); // 编译错误!nullptr 不能隐式转为 int }


3.3 问题二:模板推导歧义


#include <iostream> #include <memory> template<typename T> void deduceType(T value) { std::cout << "Deduced type: " << typeid(T).name() << std::endl; } int main() { deduceType(NULL); // 输出: int(或 long)—— 推导为整数! deduceType(nullptr); // 输出: std::nullptr_t —— 正确推导为空指针类型 // 这导致模板代码中的严重问题: // auto result = someFunction(NULL); // result 推导为 int! // auto result = someFunction(nullptr); // result 推导为 std::nullptr_t }


3.4 问题三:指针上下文以外的滥用


#include <iostream> class MyClass { public: void process(int x) { std::cout << "process(int): " << x << std::endl; } void process(int* ptr) { if (ptr) { std::cout << "process(int*): " << *ptr << std::endl; } else { std::cout << "process(int*): nullptr" << std::endl; } } }; int main() { MyClass obj; int value = 42; obj.process(NULL); // 歧义!编译器可能选择 process(int) 或 process(int*) // 不同编译器行为可能不同 // 有些编译器报错,有些选择 int 版本 obj.process(nullptr); // 明确:调用 process(int*) }


四、nullptr 的优势


4.1 精确的类型:std::nullptr_t


#include <iostream> #include <type_traits> int main() { // nullptr 有自己的类型 std::cout << std::is_same_v<decltype(nullptr), std::nullptr_t> << std::endl; // true // nullptr_t 可以隐式转换到任意指针类型 int* pi = nullptr; // OK double* pd = nullptr; // OK void (*fp)() = nullptr; // OK // nullptr_t 可以隐式转换到 bool bool b = nullptr; // OK: b = false // nullptr_t 不能隐式转换到整数 // int x = nullptr; // 编译错误! int x = 0; // 这才是正确的 // 可以进行比较 if (pi == nullptr) { } // OK if (pi == 0) { } // OK(但应该用 nullptr) if (nullptr == 0) { } // OK(但语义模糊) // sizeof std::cout << sizeof(nullptr) << std::endl; // 通常 8(与指针大小相同) }


4.2 函数重载的明确性


#include <iostream> // 重载函数:处理不同的参数类型 void handle(int value) { std::cout << "handle(int): " << value << std::endl; } void handle(double value) { std::cout << "handle(double): " << value << std::endl; } void handle(const char* str) { std::cout << "handle(const char*): " << (str ? str : "null") << std::endl; } void handle(void* ptr) { std::cout << "handle(void*): " << ptr << std::endl; } int main() { handle(42); // handle(int): 42 handle(3.14); // handle(double): 3.14 handle("Hello"); // handle(const char*): Hello // 使用 NULL:歧义! // handle(NULL); // 编译错误或警告:int vs void* 歧义 // 使用 nullptr:明确 handle(nullptr); // handle(void*): 0x0 (精确匹配 void* 版本) }


4.3 模板编程中的准确性


#include <iostream> #include <memory> #include <type_traits> // 模板函数:根据指针是否为空执行不同逻辑 template<typename T> auto safeDereference(T ptr) { // 使用 nullptr 可以正确推断 T 是指针类型 if (ptr == nullptr) { return decltype(*ptr){}; // 返回默认值 } return *ptr; } // 类型萃取:检测类型是否可作为空指针 template<typename T> struct is_nullable : std::false_type { }; template<typename T> struct is_nullable<T*> : std::true_type { }; template<typename T> struct is_nullable<std::shared_ptr<T>> : std::true_type { }; template<typename T> struct is_nullable<std::unique_ptr<T>> : std::true_type { }; int main() { int value = 42; int* ptr1 = &value; int* ptr2 = nullptr; std::cout << safeDereference(ptr1) << std::endl; // 42 std::cout << safeDereference(ptr2) << std::endl; // 0(默认 int) // nullptr_t 在模板中保持精确类型 auto np = nullptr; // decltype(np) 是 std::nullptr_t,而不是 int std::cout << std::is_same_v<decltype(np), std::nullptr_t> << std::endl; // true }


4.4 条件表达式中的安全性


#include <iostream> #include <string> std::string getValueOrDefault(const char* str, const std::string& defaultValue) { // 使用 nullptr 清晰表达空指针语义 if (str == nullptr) { return defaultValue; } return std::string(str); } // 避免布尔上下文中的歧义 class SmartPointer { int* ptr; public: explicit SmartPointer(int* p = nullptr) : ptr(p) { } // 使用 nullptr 检查比直接 if (ptr) 更明确 bool isNull() const { return ptr == nullptr; } bool isNotNull() const { return ptr != nullptr; } int* get() { return ptr; } }; int main() { std::cout << getValueOrDefault(nullptr, "default") << std::endl; // default std::cout << getValueOrDefault("Hello", "default") << std::endl; // Hello SmartPointer sp; std::cout << std::boolalpha; std::cout << "isNull: " << sp.isNull() << std::endl; // true std::cout << "isNotNull: " << sp.isNotNull() << std::endl; // false }


五、决策流程图


C++11及以上必须兼容C++03指针初始化指针比较函数参数delete后置空智能指针

需要表示空指针

代码目标标准

使用 nullptr

被迫使用 NULL 或 0

类型安全
不匹配整数参数

重载明确
精确匹配指针版本

模板推导
推导为 std::nullptr_t

代码可读性
意图一目了然

注意: 不要混用 NULL 和 nullptr
保持代码风格一致

检查场景

int* p = nullptr;

if (p == nullptr)

func(nullptr);

delete p; p = nullptr;

shared_ptr sp = nullptr;


六、迁移指南:从 NULL 到 nullptr


6.1 代码迁移模式


// ========== 旧的写法(应该避免) ========== int* oldStyle() { int* p = NULL; // 初始化 if (p != NULL) { } // 比较 if (p) { } // 布尔检查(仍然可用但不推荐) if (!p) { } // 布尔检查(仍然可用但不推荐) // 函数调用 process(p, NULL); // 容易产生歧义 return NULL; // 返回空指针 } // ========== 新的写法(推荐) ========== int* newStyle() { int* p = nullptr; // 初始化 if (p != nullptr) { } // 比较(明确) // 函数调用 process(p, nullptr); // 清晰明确 return nullptr; // 返回空指针 }


6.2 特殊情况处理


// 与 C API 交互时 #include <cstring> #include <cstdio> void cApiExample() { // C 标准库函数通常接受 NULL // 但你仍然应该传递 nullptr(可以隐式转换) FILE* file = fopen("test.txt", "r"); if (file == nullptr) { // 不是 NULL std::cout << "File not found" << std::endl; } // 某些 C 库的宏可能使用 NULL // 不要被这些影响,在 C++ 代码中坚持使用 nullptr // 变参函数中的 nullptr(如 printf) // printf("%s", nullptr); // 未定义行为! printf("%s", "(null)"); // 正确做法 }


七、总结


nullptr替代NULL是 C++11 带来的一个看似微小但影响深远的变化:


  1. 类型安全的根本差异:NULL在 C++ 中是整数常量0(或0L),本质上是一个整数;nullptr是std::nullptr_t类型,是一个真正的空指针字面量。这一差异是其他所有问题的根源。


  1. 解决的核心问题:
  • 函数重载歧义:NULL可能被解析为int而匹配错误的函数版本,nullptr精确匹配指针参数
  • 模板推导错误:auto x = NULL推导为int,auto x = nullptr推导为std::nullptr_t
  • 代码意图表达:nullptr一目了然地表示“这里处理的是指针”,NULL可能是整数也可能是指针


  1. 现代 C++ 的坚定选择:
  • 所有新代码应该完全使用nullptr,不使用NULL或0表示空指针
  • 与旧代码交互时,nullptr可以无缝替代NULL
  • 使用nullptr而不是if (ptr)或if (!ptr)进行指针判空,意图更明确
  • 将nullptr视为 C++ 类型系统走向更精确、更安全的一个标志性进步


一条简单的规则:在现代 C++ 中,任何时候你想表达“这个指针不指向任何东西”,就用nullptr。它是专门为此目的设计的,而NULL是 C 语言遗留的历史包袱。这个看似微小的选择,反映的是对类型安全和代码可维护性的认真态度。

相关新闻

  • 如何制作微信视频投票活动 可上传图文视频的投票小程序推荐 - 资讯速览
  • 嵌入式系统数据完整性守护:TI CRC控制器工作模式与中断机制详解
  • AI教材编写:15%以下查重率的实战方法论

最新新闻

  • taotoken的apikey管理与访问控制功能保障企业级应用安全
  • 2026年企业视频拍摄报价怎么对比才科学?第三方总结的同维度比价法与报价分析全指南 - 影视产业研究
  • 5分钟掌握跨平台智能语音合成:Sherpa Onnx TTS终极实战指南
  • 2026湖南工程门页配套代工甄选指南:本地工厂实力对比与择优建议 - geo交流
  • 5分钟解决Zotero文献重复:智能合并插件实战指南
  • 从“可替代”到“战略级人才”:AI原生技能图谱首次公开(含2024岗位需求热力图)

日新闻

  • 从国家条件到买方清单,深入理解 ABAP CDS 单值过滤器派生
  • 2026 年当下,齐齐哈尔专业的不锈钢闸门批发厂家哪个好,揭秘!这个工业“铁门”如何实现成本翻倍的效率提升? - 行业甄选官
  • 2026阳极氧化加工厂推荐:从设备规模看硬质氧化技术的成熟应用推荐百正机械 - 栗子测评

周新闻

  • SaaS软件行业GEO实践:AI搜索时代的品牌可见性与获客新路径
  • 什么是PCTFE?医药高端包装的“防潮王牌“材料
  • 【JVM调优实战】16-可视化利器-JConsole-VisualVM-JMC

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号