C++ 进阶学习笔记
适合目标:在完成基础入门后,补齐现代 C++ 核心能力,能应对中高级面试题,也能理解真实工程代码。
学习重点:RAII、智能指针、右值引用、移动语义、模板、STL 深入、并发、设计原则。
学习原则:每学一个进阶特性,都要回答“它是为了解决什么问题”。
目录
- 进阶学习主线
- RAII
- 智能指针
- 左值、右值与移动语义
- 完美转发
- 模板与泛型编程
- STL 深入
- lambda 与函数对象
- 并发基础
- 设计与工程实践
- 高频面试题
- 学习路径建议
- 一页速记总结
1. 进阶学习主线
C++ 进阶不是继续记更多语法,而是理解现代 C++ 怎样解决下面这些问题:
- 资源怎么安全管理
- 对象怎么少拷贝、少分配
- 泛型代码怎么既复用又高性能
- 多线程代码怎么避免竞态和死锁
- 大型工程里怎么控制复杂度
所以进阶阶段的主线可以总结成:
资源管理 -> 性能优化 -> 泛型抽象 -> 并发控制 -> 工程设计
2. RAII
RAII 全称是 Resource Acquisition Is Initialization。
核心思想:
- 资源在对象构造时获取
- 资源在对象析构时释放
- 用对象生命周期绑定资源生命周期
这样做的最大价值是:
- 即使函数提前 return,也能自动释放资源
- 即使发生异常,也更容易保证清理逻辑执行
示例:
class FileGuard {
public:
FileGuard(const string& path) {
cout << "open file: " << path << endl;
}
~FileGuard() {
cout << "close file" << endl;
}
};
void work() {
FileGuard file("a.txt");
}
面试里常见表达:
RAII 是现代 C++ 资源管理的核心思想,智能指针、lock_guard、本地对象清理都建立在它上面。
3. 智能指针
现代 C++ 最重要的一块之一,就是尽量少手写裸 new/delete。
3.1 unique_ptr
独占所有权。
std::unique_ptr<int> p = std::make_unique<int>(10);
特点:
- 同一时刻只有一个拥有者
- 不可拷贝,可移动
- 开销小,最常优先使用
3.2 shared_ptr
共享所有权,内部维护引用计数。
std::shared_ptr<int> p1 = std::make_shared<int>(10);
std::shared_ptr<int> p2 = p1;
特点:
- 多个对象可共同持有同一资源
- 最后一个持有者析构时释放资源
- 有额外计数成本
3.3 weak_ptr
解决 shared_ptr 循环引用问题。
std::weak_ptr<int> wp;
使用场景:
- 观察资源但不拥有资源
- 打破双向引用
3.4 智能指针选型
优先级通常是:
- 能用栈对象就别上堆
- 必须动态分配时优先
unique_ptr - 确实需要共享所有权再用
shared_ptr - 观察关系用
weak_ptr
4. 左值、右值与移动语义
这一部分是很多进阶面试题的核心。
4.1 左值和右值
先用最实用的方式理解:
- 左值通常有名字、能取地址、生命周期较稳定
- 右值通常是临时对象,马上就会销毁
int a = 10;
int b = a + 5;
这里:
a是左值a + 5产生的临时结果更接近右值
4.2 为什么要有移动语义
因为很多对象内部持有堆内存,如果每次传递都深拷贝,成本很高。
移动语义的思想是:
既然源对象马上不用了,那就把资源“搬过来”,不要重新拷贝一遍。
4.3 右值引用
int&& x = 10;
右值引用主要是为移动语义和完美转发服务的,不是单纯为了多一个引用语法。
4.4 移动构造和移动赋值
class Buffer {
public:
Buffer(Buffer&& other) noexcept {
}
Buffer& operator=(Buffer&& other) noexcept {
return *this;
}
};
理解重点:
- 移动是“转移资源所有权”
- 被移动对象要保持“可析构、可赋值”的有效状态
noexcept往往影响容器是否愿意使用移动操作
4.5 std::move
std::move 本身不移动,它只是把对象转成右值语义,告诉编译器“这个对象可以被搬走”。
5. 完美转发
完美转发解决的问题是:
模板函数接收到参数后,怎样保持它原本的左值/右值属性继续传下去。
template <typename T>
void wrapper(T&& value) {
func(std::forward<T>(value));
}
要点:
T&&在模板推导场景下可能是万能引用std::forward<T>用于保留值类别- 常用于工厂函数、通用封装和容器实现
6. 模板与泛型编程
6.1 函数模板
template <typename T>
T add(T a, T b) {
return a + b;
}
6.2 类模板
template <typename T>
class Box {
public:
T value;
};
模板的价值:
- 代码复用
- 编译期生成具体类型版本
- 通常兼顾抽象能力和性能
6.3 模板实例化
模板不是写完就真的生成机器码,通常在使用到具体类型时才实例化。
6.4 变参模板
template <typename... Args>
void log(Args... args) {
}
应用场景:
- 通用打印
emplace_back- 工厂封装
7. STL 深入
7.1 vector 扩容机制
vector 底层是一段连续内存。
关键点:
- 扩容时可能整体搬迁
- 原来的迭代器、引用、指针可能失效
- 尾插通常均摊
O(1)
7.2 map 和 unordered_map 选型
- 需要有序遍历用
map - 更关注平均查找效率常用
unordered_map - 哈希冲突严重时
unordered_map表现会波动
7.3 迭代器失效
这是非常高频的面试和 bug 来源。
例如:
vector扩容后迭代器可能失效erase后当前位置及其后的迭代器可能失效- 不同容器的失效规则不同
7.4 常用算法
sort(nums.begin(), nums.end());
reverse(nums.begin(), nums.end());
auto it = find(nums.begin(), nums.end(), 3);
进阶学习不能只会容器,还要会配合 <algorithm> 使用。
8. lambda 与函数对象
8.1 lambda
auto add = [](int a, int b) {
return a + b;
};
优势:
- 适合短逻辑内联定义
- 和 STL 算法天然配合
- 能捕获上下文变量
8.2 捕获列表
int x = 10;
auto f1 = [x]() { return x; };
auto f2 = [&x]() { x++; };
区别:
[x]值捕获[&x]引用捕获
8.3 仿函数
重载 operator() 的对象。
class Add {
public:
int operator()(int a, int b) const {
return a + b;
}
};
9. 并发基础
9.1 线程
#include <thread>
void task() {}
std::thread t(task);
t.join();
9.2 互斥锁
#include <mutex>
std::mutex mtx;
共享数据修改时要注意竞态条件。
9.3 lock_guard
{
std::lock_guard<std::mutex> lock(mtx);
}
这又是 RAII 的典型应用。
9.4 死锁
常见成因:
- 多把锁获取顺序不一致
- 忘记释放锁
- 锁粒度设计不合理
9.5 原子操作
#include <atomic>
std::atomic<int> cnt = 0;
适用于简单共享状态,但它不能替代所有同步问题。
10. 设计与工程实践
10.1 Rule of Three / Five
如果类需要自定义以下资源管理函数中的一些,通常要整体考虑:
- 析构函数
- 拷贝构造
- 拷贝赋值
- 移动构造
- 移动赋值
现代 C++ 更常说 Rule of Five。
10.2 组合优于继承
工程里不要为了“像面向对象”就滥用继承。
很多时候组合更稳:
- 耦合更低
- 更容易替换
- 结构更清晰
10.3 接口设计建议
- 明确所有权归属
- 尽量用值语义或智能指针表达生命周期
- 尽量减少裸指针暴露范围
- 常量语义尽量通过
const表达清楚
11. 高频面试题
11.1 shared_ptr 为什么可能有循环引用
因为两个对象互相持有 shared_ptr,引用计数都不会归零,导致资源无法释放。解决方式通常是其中一边改成 weak_ptr。
11.2 什么是移动语义
本质是把原对象持有的资源转移给目标对象,避免深拷贝,提高性能,常与右值引用一起出现。
11.3 std::move 一定会触发移动吗
不一定。它只是一个类型转换,真正是否调用移动构造/移动赋值,还取决于目标类型是否支持移动以及上下文是否匹配。
11.4 vector 扩容有什么影响
可能重新申请更大的连续空间并搬迁元素,导致原有地址、引用、迭代器失效。
11.5 模板和宏的区别
- 宏是预处理文本替换
- 模板是编译期类型化机制
- 模板更安全、可检查、可推导
12. 学习路径建议
建议按这个顺序推进:
- 先吃透 RAII 和智能指针
- 再补左值右值、移动语义、完美转发
- 接着学模板、变参模板、STL 深入
- 然后补线程、锁、原子变量
- 最后通过项目代码和面试题反复巩固
如果你的目标是求职,进阶阶段一定要会把“概念”翻译成“工程风险”:
- 为什么这里不能裸指针共享
- 为什么这里要避免不必要拷贝
- 为什么这里需要锁或原子变量
- 为什么这里迭代器会失效
13. 一页速记总结
- RAII 是现代 C++ 资源管理核心
- 智能指针核心是表达所有权与自动释放
- 右值引用和移动语义核心是避免不必要深拷贝
- 完美转发核心是保留参数原始值类别
- 模板核心是编译期泛型复用
- STL 进阶重点是容器特性、迭代器失效、算法配合
- 并发重点是竞态、锁、死锁、原子性
- 工程实践重点是生命周期、所有权、接口边界