转向现代C++——优先选用nullptr而不是0和NULL

文章目录

    • [优先选用 nullptr,而非 0 或 NULL](#优先选用 nullptr,而非 0 或 NULL)

优先选用 nullptr,而非 0 或 NULL

优先选用 nullptr,而非 0NULL,其核心原因可以总结为一句话:0和NULL的本质是整型,而nullptr 的本质才是真正的指针。**

在模板编程、函数重载以及类型推导中,把整型当指针用会引发各种让人抓狂的二义性和编译错误。

由于 0intNULL 在大多数编译器下被定义为 0 或者 0L(长整型),它们在重载决议中会被优先识别为数值,而不是指针。

cpp 复制代码
#include <iostream>

// 重载函数 1:接收整型
void f(int x) {
    std::cout << "调用了 f(int)\n";
}

// 重载函数 2:接收空指针
void f(void* p) {
    std::cout << "调用了 f(void*)\n";
}

int main() {
    // 1. 传入数值 0
    f(0);       // 输出:调用了 f(int) 
                // 原因:0 是字面量整型,完美匹配 f(int)

    // 2. 传入宏 NULL
    f(NULL);    // 输出:调用了 f(int) 
                // 原因:NULL 实际上就是 0 或 0L,依然被当作整型处理

    // 3. 用户的本意是想传空指针,但上面两个都调用错了!
    
    // 4. 传入真正代表指针的 nullptr
    f(nullptr); // 输出:调用了 f(void*)
                // 原因:nullptr 的类型是 std::nullptr_t,它能隐式转换为任何指针类型,
                // 但绝对不会转换为整型!
}

当模板进行自动类型推导时,0 和 NULL 的整型身份会彻底暴露,导致原本合法的代码直接编译报错

cpp 复制代码
//假设我们要写一个通用的"锁管理执行器"模板,它负责:上锁 -> 执行函数 -> 解锁。
#include <mutex>
#include <memory>
#include <utility>

std::mutex mtx;

// 模拟三个不同参数的底层函数
void g1(int* p) {}
void g2(std::shared_ptr<int> p) {}
void g3(int x) {}

// ==================== 泛型执行器模板 ====================
template<typename FuncType, typename PtrType>
void logAndExecute(FuncType func, PtrType param) {
    std::lock_guard<std::mutex> lock(mtx); // 上锁
    func(param);                           // 执行函数
}

int main() {
    // ---- 场景 A:不使用模板,直接调用(由于隐式转换,强行能跑) ----
    g1(0);       // 勉强通过,0 可以隐式转换为 int*
    g1(NULL);    // 勉强通过
    g1(nullptr); // 正确通过


    // ---- 场景 B:使用模板(灾难降临) ----
    
    // 1. 尝试传入 nullptr
    logAndExecute(g1, nullptr); // 成功!
    // 编译器推导:PtrType 为 std::nullptr_t。
    // 在模板内部执行:g1(nullptr),std::nullptr_t 隐式转换为 int*,完美!

    // 2. 尝试传入 0
    // logAndExecute(g1, 0); 
    /* 
       【编译报错!】
       编译器的内心活动:
       1. 传入了数值 0,因此我推导 PtrType 的实际类型为 int。
       2. 实例化模板内部代码:func(param) -> 变成了 g1(int x)。
       3. 糟糕!g1 接收的参数是 int* 类型的指针,而你传给它一个 int 类型的变量(param)!
       4. 整型变量是绝对不能隐式转换成指针的(字面量 0 可以,但装进变量里就不行了)。
       5. 报错:无法将 'int' 转换为 'int*'
    */

    // 3. 尝试传入 NULL
    // logAndExecute(g2, NULL);
    /* 
       【编译报错!】
       同样的道理,NULL 被推导为 long 或 int。
       在模板内部试图把一个整型变量传给 std::shared_ptr<int>,直接引发语法错误。
    */
}

除了编译期的绝对安全,nullptr 还能提升代码的清晰性。

cpp 复制代码
// 假设你正在审查别人的代码,看到了这一行:
auto result = findRecord( /* 参数 */ );

//不清楚返回的是整数类型还是指针类型
if (result == 0) {
    // ...
}

//返回的肯定是指针类型
if (result == nullptr) {
    // ...
}
相关推荐
名字还没想好☜5 小时前
Python f-string 进阶:数字格式化、对齐填充、调试 = 号与嵌套表达式
开发语言·数据库·python·字符串格式化·f-string
·薯条大王5 小时前
经济实惠玩云服务器|一台云服务器多人共用,子账号配置教程
java·linux·运维·服务器·汇编·c++·python
一壶浊酒..8 小时前
Scrape Webpack练习
开发语言·javascript·webpack
命运之光9 小时前
【C语言完整代码】就诊信息管理系统
java·c语言·开发语言
命运之光9 小时前
【C语言完整代码】图书管理系统:图书馆场景下的增删改查实战
c语言·开发语言
猿长大人9 小时前
C# | Serilog 新手入门
开发语言·c#·.net·log
hansang_IR10 小时前
【题解】LC:倍增 / 区间并查集(Range Parallel Unionfind)
c++·算法·并查集
东东最爱敲键盘10 小时前
day7.c++继承
c++
在世修行11 小时前
从零打造 C# 工业视觉检测系统(七):串口通信 SerialPort 封装与开发
开发语言·c#·modbus rtu·rs232/rs485
沫璃染墨11 小时前
《从零入门Linux系统篇(十五):系统工具篇·六——GDB调试器详解:从程序执行控制到高级调试技巧》
linux·运维·服务器·c语言·c++