这一章从 C 的指针过渡到 C++ 的引用和资源管理。引用让参数传递更自然,new/delete 让对象生命周期更灵活,智能指针则是后面写安全 C++ 代码时应该优先考虑的工具。

2.0 指针与引用

引用可以理解成对象的别名。它不像指针那样保存一个可以随时改的地址,而是初始化后绑定到某个对象。

对比 指针 引用
是否可为空 可以为空 正常引用不应为空
是否可改指向 可以 初始化后不能改绑
使用语法 *p / p-> 像普通变量一样
适合场景 可选对象、动态对象、数组遍历 参数传递、别名、返回对象

2.1 引用基本特性

引用必须初始化,之后它就是原对象的另一个名字。

1
2
3
4
5
6
7
8
9
10
#include <iostream>

int main() {
int value = 10;
int &ref = value;

ref = 20;
std::cout << value << std::endl;
return 0;
}

复盘重点:修改引用就是修改原对象。

2.2 引用作函数参数

引用参数避免复制,也能让函数修改外部对象。只读参数要加 const,表达“不修改”。

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>

void swap_value(int &a, int &b) {
int tmp = a;
a = b;
b = tmp;
}

void print_name(const std::string &name) {
std::cout << name << std::endl;
}
写法 适合场景
T value 小对象、需要复制
T &value 需要修改外部对象
const T &value 大对象只读传递

2.3 引用作返回值

引用返回值必须指向仍然存在的对象。不能返回局部变量引用。

1
2
3
4
5
#include <vector>

int &at(std::vector<int> &nums, int index) {
return nums.at(index);
}

错误思路:

1
2
3
4
int &bad() {
int value = 10;
return value;
}

局部变量在函数返回后销毁,返回它的引用会形成悬空引用。

2.4 常量引用特性

常量引用可以绑定临时对象,也可以避免复制。C++ 里函数参数常见写法是 const T &

1
2
3
void show(const std::string &text) {
std::cout << text << std::endl;
}

安全复盘:只读接口尽量写成 const,这样对象不会被函数悄悄改掉。

2.5 引用、常量引用与指针

如果参数必须存在,用引用;如果参数可以不存在,用指针并检查空;如果只读,用 const

需求 推荐
必须传入并修改 T &
必须传入但不修改 const T &
可以不传 T * 或智能指针
动态共享所有权 std::shared_ptr<T>
独占所有权 std::unique_ptr<T>

2.6 数组引用样式

数组引用可以保留数组长度。

1
2
3
4
5
6
7
8
9
#include <iostream>

template <size_t N>
void print_array(const int (&arr)[N]) {
for (size_t i = 0; i < N; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}

这比只传 int * 更安全,因为长度没有丢。

2.7 内存分区

C++ 对象也要落在具体内存区域里。

区域 内容
栈区 局部对象,自动构造析构
堆区 new 创建对象,需要释放或交给智能指针
全局/静态区 全局对象、静态对象
只读区 字符串字面量、常量数据
代码区 成员函数和普通函数机器码

2.8 newdelete

new 会分配内存并调用构造函数;delete 会调用析构函数并释放内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

class User {
public:
User() { std::cout << "construct\n"; }
~User() { std::cout << "destruct\n"; }
};

int main() {
User *user = new User();
delete user;
user = nullptr;
return 0;
}

复盘重点:new[] 对应 delete[]new 对应 delete,不能混用。

2.9 内存异常处理

内存申请可能失败,构造函数也可能抛异常。现代 C++ 更推荐 RAII 和智能指针,让资源自动释放。

3.0 shared_ptr

std::shared_ptr 通过引用计数共享对象所有权。适合多个对象共同持有同一资源,但要警惕循环引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <memory>

struct Node {
int value;
explicit Node(int v) : value(v) {}
};

int main() {
std::shared_ptr<Node> p1 = std::make_shared<Node>(10);
std::shared_ptr<Node> p2 = p1;
std::cout << p1.use_count() << std::endl;
return 0;
}

3.1 unique_ptr

std::unique_ptr 表示独占所有权,不能复制,只能移动。它适合表达“这个资源只有我负责”。

1
2
3
4
5
#include <memory>

std::unique_ptr<int> make_value() {
return std::make_unique<int>(42);
}

本章复盘重点

方向 问题
引用 是否会产生悬空引用
指针 是否为空,是否越界,是否已经释放
堆对象 谁负责释放
智能指针 所有权是独占还是共享
安全边界 是否能避免裸指针和手动释放

学习检查清单

检查项 状态
能区分指针和引用的使用场景 待复盘
能写出引用参数和常量引用参数 待复盘
能说明为什么不能返回局部变量引用 待复盘
能正确配对 new/deletenew[]/delete[] 待复盘
能说明 unique_ptrshared_ptr 的区别 待复盘