1More Effective C++

指针与引用

基础辨析

指针vs引用选择策略类型安全空值处理

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第1章:指针与引用的基础辨析

一、导读

1.1 本章学习目标

第1章辨析 C++ 中最基础也最容易混淆的两个概念:指针和引用。Meyers 在此给出了何时使用指针、何时使用引用的清晰指导。

本章核心学习目标:

  • 理解指针和引用的本质区别:空值、重新绑定、语法差异
  • 掌握选择策略:什么时候用指针?什么时候用引用?
  • 理解类型安全和空值处理

1.2 为什么本章如此重要

指针和引用的选择影响代码的安全性、可读性和性能。错误的选择会导致空指针解引用、内存泄漏等问题。

1.3 核心问题引导

引用和指针在底层实现上有什么区别?

为什么函数参数优先使用 const 引用?

什么时候必须使用指针?


二、核心概念详解

2.1 指针 vs 引用的区别

cpp
// 引用: 必须是有效对象的别名,不能为空,不能重新绑定
int x = 42;
int& ref = x;    // ref 是 x 的别名
// int& null_ref; // 错误: 引用必须初始化
// ref = y;       // 这是赋值,不是重新绑定

// 指针: 可以为空,可以重新指向
int* ptr = &x;   // ptr 指向 x
ptr = nullptr;   // OK: 指针可以为空
int y = 100;
ptr = &y;        // OK: 指针可以重新指向

// 语法差异:
*ptr = 10;  // 需要解引用
ref = 10;   // 直接使用

// 底层实现:
// 引用通常用指针常量实现: int* const ref = &x;
// 但编译器可以做更多优化(如直接替换为原变量)

2.2 选择策略

cpp
// 使用引用的场景:
// 1. 函数参数(避免拷贝,表达"不为空"的语义)
void process(const std::string& name);  // 不修改,不拷贝
void modify(std::vector<int>& data);    // 需要修改

// 2. 操作符重载
std::ostream& operator<<(std::ostream& os, const Widget& w);

// 3. 返回左值
int& getFirstElement(std::vector<int>& vec) {
    return vec[0];  // 返回引用,允许修改
}

// 使用指针的场景:
// 1. 可能为空
Widget* findWidget(int id) {
    // 可能找不到,返回 nullptr
}

// 2. 需要重新指向
Widget* current = nullptr;
for (auto& w : widgets) {
    if (w.isActive()) {
        current = &w;  // 指向不同的 widget
        break;
    }
}

// 3. 数组操作
void processArray(int* arr, size_t size) {
    for (size_t i = 0; i < size; ++i)
        arr[i] *= 2;
}

2.3 避免返回指向局部变量的引用

cpp
// 错误: 返回局部变量的引用
const std::string& getGreeting() {
    std::string greeting = "Hello";
    return greeting;  // greeting 在函数结束时销毁!
}

// 正确: 返回值(利用移动语义)
std::string getGreetingSafe() {
    std::string greeting = "Hello";
    return greeting;  // 移动构造或 NRVO
}

// 正确: 返回成员变量的引用
class Config {
    std::string name;
public:
    const std::string& getName() const { return name; }  // OK: 成员变量生命周期足够长
};

三、实战案例

3.1 案例一:引用和指针的性能差异

cpp
#include <benchmark/benchmark.h>

// 引用版本: 编译器更容易优化
void addRef(const int& a, const int& b, int& result) {
    result = a + b;
}

// 指针版本: 编译器需要处理别名问题
void addPtr(const int* a, const int* b, int* result) {
    *result = *a + *b;
}

// 引用版本通常生成更高效的代码
// 因为编译器知道引用不会为空,不会有别名问题

四、本章小结

引用不能为空、不能重新绑定,适合表达"必不为空"的语义

指针可以为空、可以重新指向,适合可选值和数组操作

函数参数优先使用 const 引用,避免不必要的拷贝


五、思考与练习

思考题

为什么 C++ 需要引用?指针不够用吗?

右值引用(&&)和左值引用(&)有什么区别?

编程练习

实现一个可选类型(Optional),内部使用指针但对外提供引用接口。

重写一个使用指针的函数,改为使用引用。


1/6指针与引
0%