乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

nullptr 与空指针

定义与作用

C++11 引入了空指针字面量 nullptr,类型为 std::nullptr_t。它的出现解决了 C 风格空指针常量 NULL 和 0 的两大痛点:

  1. 类型歧义:NULL 通常是 0 的宏定义,在重载决议中会被解析为 int 而非指针
  2. 模板推导混乱:NULL 作为模板参数时被推导为 int,与指针预期不符

nullptr 可以隐式转换为任何指针类型或成员指针类型,但不能隐式转换为整型——这从根本上消除了歧义。

核心原理

重载决议对比

重载决议示例

void f(int);        // 重载 1:整型
void f(char*);      // 重载 2:字符指针

f(NULL);    // 调用 f(int) — 大概率不是你的意图
f(nullptr); // 调用 f(char*) — 这就是你要的

nullptr_t 类型关系

完整示例

示例一:空指针重载陷阱与 nullptr 的解决方案

场景说明:一个命令行处理系统,有指针版本和整型版本的函数重载。用 NULL 会误调用整型版本。

#include <iostream>
#include <string>

class CommandProcessor {
public:
    // 整型重载:处理数值型命令 ID
    void execute(int cmd_id) {
        std::cout << "[整型版本] 执行命令 ID: " << cmd_id << std::endl;
    }

    // 指针重载:通过字符串命令名执行
    void execute(const char* cmd_name) {
        if (cmd_name) {
            std::cout << "[指针版本] 执行命令: " << cmd_name << std::endl;
        } else {
            std::cout << "[指针版本] 空命令,执行默认行为" << std::endl;
        }
    }

    // double 重载(添加更多歧义场景)
    void execute(double value) {
        std::cout << "[浮点版本] 处理值: " << value << std::endl;
    }
};

template <typename T>
void log_pointer_type(T* ptr) {
    if (ptr == nullptr) {
        std::cout << "空指针(nullptr)" << std::endl;
    } else {
        std::cout << "有效指针: " << static_cast<const void*>(ptr) << std::endl;
    }
}

int main() {
    CommandProcessor proc;

    std::cout << "===== 重载决议对比 =====" << std::endl;

    std::cout << "调用 execute(NULL):" << std::endl;
    proc.execute(NULL);  // 危险!大概率调用 execute(int),传入 0
    // 输出: [整型版本] 执行命令 ID: 0

    std::cout << "调用 execute(nullptr):" << std::endl;
    proc.execute(nullptr);  // 正确!调用 execute(const char*)
    // 输出: [指针版本] 空命令,执行默认行为

    std::cout << "\n===== 显式调用对比 =====" << std::endl;

    const char* str = "status";
    proc.execute(str);      // 明确调用指针版本

    int cmd = 100;
    proc.execute(cmd);      // 明确调用整型版本

    // NULL 和 nullptr 的模板推导差异
    std::cout << "\n===== 模板推导 =====" << std::endl;
    log_pointer_type(nullptr);  // T 推导为某种类型,指针值为空

    // sizeof 差异
    std::cout << "\n===== 类型大小 =====" << std::endl;
    std::cout << "sizeof(NULL):     " << sizeof(NULL) << " (通常是 sizeof(int))" << std::endl;
    std::cout << "sizeof(nullptr):  " << sizeof(nullptr) << " (平台相关)" << std::endl;

    // nullptr 的类型判断
    std::cout << "nullptr 是指针? "
              << std::is_pointer<decltype(nullptr)>::value << std::endl;
    std::cout << "nullptr 是 nullptr_t? "
              << std::is_null_pointer<decltype(nullptr)>::value << std::endl;

    return 0;
}

预期输出:

===== 重载决议对比 =====
调用 execute(NULL):
[整型版本] 执行命令 ID: 0
调用 execute(nullptr):
[指针版本] 空命令,执行默认行为

===== 显式调用对比 =====
[指针版本] 执行命令: status
[整型版本] 执行命令 ID: 100

===== 模板推导 =====
空指针(nullptr)

===== 类型大小 =====
sizeof(NULL):     4 (通常是 sizeof(int))
sizeof(nullptr):  8 (平台相关)
nullptr 是指针? false
nullptr 是 nullptr_t? true

逐段分析:

  • proc.execute(NULL) 调用的是 execute(int),因为 NULL 展开为 0(或 __null),被优先解析为整型
  • proc.execute(nullptr) 调用 execute(const char*),因为 nullptr 不能隐式转为 int,只能转指针
  • 模板函数 log_pointer_type 中 nullptr 正确推导为指针
  • std::is_null_pointer 可以检测 std::nullptr_t 类型(C++14 起)
  • sizeof(nullptr) 在 64 位平台通常为 8 字节

示例二:智能指针与 nullptr 的配合

#include <iostream>
#include <memory>
#include <vector>

struct Task {
    int id;
    std::string description;
    Task(int i, std::string d) : id(i), description(std::move(d)) {}

    void run() const {
        std::cout << "运行任务 #" << id << ": " << description << std::endl;
    }
};

// 用 nullptr 表示"未找到任务"
std::unique_ptr<Task> find_task(const std::vector<std::unique_ptr<Task>>& tasks,
                                 int target_id) {
    for (const auto& t : tasks) {
        if (t && t->id == target_id) {
            // 不能直接返回 t(它是 const& unique_ptr)
            return nullptr;  // 简化处理:实际项目中可能需特殊设计
        }
    }
    return nullptr;  // 清晰表达"未找到"
}

int main() {
    std::vector<std::unique_ptr<Task>> tasks;

    tasks.push_back(std::make_unique<Task>(1, "编译前端"));
    tasks.push_back(std::make_unique<Task>(2, "代码生成"));
    tasks.push_back(std::make_unique<Task>(3, "链接"));

    // nullptr 用于检查
    auto task = find_task(tasks, 2);
    if (task == nullptr) {
        std::cout << "任务未找到" << std::endl;
    } else {
        task->run();
    }

    // 安全的空指针检查
    for (const auto& t : tasks) {
        if (t != nullptr) {   // 明确检查,替代 if(t) 也合法
            t->run();
        }
    }

    // nullptr 用于重置智能指针
    auto ptr = std::make_unique<Task>(99, "测试");
    std::cout << "\n重置前: " << (ptr ? "有值" : "空") << std::endl;
    ptr = nullptr;  // 释放资源并将 ptr 置空
    std::cout << "重置后: " << (ptr ? "有值" : "空") << std::endl;

    // shared_ptr 同理
    auto sptr = std::make_shared<Task>(100, "共享任务");
    if (sptr != nullptr) {
        sptr->run();
    }

    return 0;
}

预期输出:

任务未找到
运行任务 #1: 编译前端
运行任务 #2: 代码生成
运行任务 #3: 链接

重置前: 有值
重置后: 空
运行任务 #100: 共享任务

逐段分析:

  • 现代 C++ 中 nullptr 与智能指针无缝配合:== nullptr、!= nullptr、= nullptr
  • if (ptr) 和 if (ptr != nullptr) 都可判断智能指针是否为空(推荐显式写法)
  • ptr = nullptr 会先释放智能指针持有的资源再置空
  • nullptr 让返回值语义明晰:函数返回空智能指针时直接 return nullptr

易错场景与面试考点

易错场景

场景错误表现正确做法
NULL 在重载函数中误匹配 int调用了不该调用的重载版本用 nullptr
NULL 作为模板实参被推导为 int泛型代码行为异常用 nullptr
老代码中 if (ptr == NULL)混淆(但通常不会出错)改为 if (ptr == nullptr)
用 0 或 '\0' 表示空指针语义模糊统一用 nullptr
忘记检查 nullptr空指针解引用导致崩溃智能指针用 if (ptr)、原始指针判空

常见面试问题

  1. nullptr 有什么优势于 NULL 和 0?——类型安全:nullptr 是 std::nullptr_t 类型,只能转为指针,不会意外匹配整型重载;避免模板推导错误。

  2. nullptr 的类型是什么?能取其地址吗?——std::nullptr_t。nullptr 是右值,不能取地址。

  3. if (ptr) 和 if (ptr != nullptr) 的区别?——语义等价,if (ptr) 依赖隐式 bool 转换。现代风格推荐显式 != nullptr 更清晰表达意图。

  4. char* p = 0 合法吗?int* p = NULL 呢?——都合法。0 是空指针常量,NULL 通常定义为 0 或 (void*)0。但这是历史写法,应替换为 nullptr。

  5. C++11 之前如何实现类似 nullptr 的类型安全?——通过自定义 nullptr_t 类 + 隐式转换运算符。标准 std::nullptr_t 解决了各库各自造轮子的问题。

小结

  • 永远使用 nullptr 替代 NULL 和 0 表示空指针
  • nullptr 的类型是 std::nullptr_t,只能隐式转换为指针类型
  • 在重载决议和模板推导中,nullptr 行为确定且正确
  • 现代 C++ 中,空指针检查推荐显式 != nullptr
上一页
const 与 constexpr
下一页
类型推导 auto 与 decltype