转向现代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) {
    // ...
}
相关推荐
ZC跨境爬虫1 小时前
跟着 MDN 学 JavaScript day_1:什么是 JavaScript?
开发语言·前端·javascript·ecmascript
apocelipes1 小时前
GNU GCC 多版本函数扩展
c语言·c++·linux编程
qq_2518364571 小时前
基于java Web 日化商超库存管理系统设计与实现
java·开发语言·前端
破土士V1 小时前
【Java基础语法10】继承、多态、抽象类接口、字符串与异常等
java·开发语言
代码中介商1 小时前
C++完美转发与引用折叠全解析
开发语言·c++
KobeSacre1 小时前
JVM ZGC
java·开发语言·jvm
caimouse1 小时前
ReactOS 部分编译指南
开发语言
Chase_______2 小时前
【Java基础 | 13】IO 流(下):缓冲流、转换流、序列化与综合案例
java·开发语言
弹简特2 小时前
【零基础学Python-收尾】10-Python第三方库的安装介绍
开发语言·python
雪度娃娃2 小时前
ASIO异步通信——多线程模型
开发语言·网络·c++·php