转向现代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) {
    // ...
}
相关推荐
郝学胜_神的一滴18 小时前
CMake 30:循环语法全解|foreach_while双循环精讲、迭代技巧与实战避坑指南
c++·cmake
卷无止境3 天前
C++ 的Eigen 库全解析
c++
卷无止境3 天前
现代 C++特性大盘点:一门脱胎换骨的老语言
c++·后端
郝学胜_神的一滴3 天前
CMake 27:缓存变量的特性、语法、类型与实操全解
c++·cmake
博客18005 天前
酷宝的使用方法,超好用的免费界面库,C++、MFC可用
c++·mfc·界面库·库来帮·酷宝
郝学胜_神的一滴5 天前
CMake 026:属性体系精讲、四大作用域全解 & 实战代码落地
c++·cmake
众少成多积小致巨6 天前
JNI (Java Native Interface) 技术手册中文参考指南
android·java·c++
clint45610 天前
C++进阶(1)——前景提要
c++
夜悊10 天前
C++代码示例:进制数简单生成工具
c++
郝学胜_神的一滴10 天前
CMake 021: IF 条件判据详诠
c++·cmake