C++ Primer / 学习首页
学习首页 知识地图 速查表
C++ Primer 5E / 零基础课程

从第一行代码,走到能解释、能调试、能设计。

这不是章节目录,而是一套循序渐进的学习课程。每个知识点都按“为什么需要它、代码怎样运行、哪里最容易错、如何练习”展开。

#include <iostream>

int main() {
  int total = 0;
  for (int n : {3, 5, 7}) total += n;
  std::cout << total << '\\n';
}
先猜输出,再运行模型
19章学习主线
201个细分学习单元
4个浏览器实验
76个理解检查点
YOUR LEARNING PATH

先让程序跑起来,再给它建立模型。

每章都可以单独阅读,也能沿推荐顺序继续。

01 / FOUNDATIONS

语言地基

从输入输出、类型、表达式到函数,把“能编译”变成能解释。

进入第 1 章 →
02 / LIBRARY

标准库

用 string、vector、迭代器和算法解决真实的数据处理问题。

进入第 8 章 →
03 / DESIGN

类与资源

从拷贝控制到继承,把对象的生命周期和接口边界讲清楚。

进入第 13 章 →
04 / ADVANCED

泛型与工具

模板、tuple、正则、随机数和异常处理,连接到大型程序。

进入第 16 章 →
START HERE

今天的推荐阅读

用 20 分钟走完一个小闭环。

第 2 章 / 变量和基本类型

先把“值、类型、对象、作用域”分开,再看 auto、const 和引用如何改变表达方式。

02.01

类型决定怎样解释位

同一个字节可以是整数、字符或布尔值。

→
02.02

引用是别名,不是新对象

用一张状态图追踪赋值和修改。

→
开始这一章
BROWSER LAB / 01

vector 会怎样长大?

改变一个输入,观察 size 和 capacity 的关系。它是教学模型,用来建立预测,而不是模拟某个特定实现。

size = 3 · capacity ≈ 4 · 预测扩容 3 次
CHECK YOUR MODEL

不要只读过,要能说出原因。

答案可展开,进度不会计分。

SELF CHECK

同一个 vector 为什么 size 和 capacity 不一定相等?

size 是当前元素数,capacity 是无需重新分配即可容纳的元素数;扩容策略由实现决定。
SELF CHECK

引用和指针最关键的区别是什么?

引用通常必须绑定到对象且不能改绑;指针保存地址,可以为空并且可以重新指向。
SELF CHECK

为什么基类析构函数常常要写 virtual?

通过基类指针销毁派生对象时,需要动态绑定到派生类析构路径,避免资源泄漏或未定义行为。
THE WHOLE BOOK

把 19 章放进一张学习地图。

每一部分解决一个更大的问题。

QUICK REFERENCE

学习时最常查的几张表。

先理解,再把这里当作查阅工具。

所有权

T:对象自己拥有资源;unique_ptr:唯一拥有;shared_ptr:共享拥有;weak_ptr:只观察不拥有。

参数

T:小对象复制;const T&:只读大对象;T&:必须修改;T*:可能为空或需要改指向。

容器

vector:默认序列;map:有序键值;unordered_map:哈希查找;set:唯一键集合。

CHAPTER 16 / 类设计工具

模板与泛型编程

用函数模板、类模板、模板实参推断和可变参数模板表达通用算法。·11 个细分单元·建议分 2-3 次阅读
教材对应印刷页:577-630 页 · 本书主线按 C++11 讲解;遇到更新标准的写法会特别注明。
本章先回答一个问题: 模板把“类型”变成算法的输入。错误通常发生在实例化时,所以要让约束和命名尽量清楚。
下面每个单元包含概念解释、代码、易错点与阅读提示。
零基础读法:先读概念,再预测代码会做什么;然后逐行追踪输入和变量状态,最后不看示例自己复述。遇到陌生词,可以回到第 1-3 章查类型、作用域和对象生命周期。

详细课程

01 · 函数模板

模板描述一套适用于多种类型的代码。编译器在调用处推断 T,再实例化具体版本;错误往往出现在实例化阶段。

template <typename T>
const T& larger(const T& a, const T& b) {
  return a < b ? b : a;
}
容易踩坑:模板不是运行时魔法,它会生成具体类型的函数。
自测读法:先遮住代码口述它会做什么;再指出关键变量、边界条件和可能失效的对象。若说不清,回到上面的概念逐句追踪。

02 · 类模板与成员模板

类模板可以把类型作为参数,例如 vector。成员函数也可以是模板,模板参数可以来自类本身或调用本次函数。

template <typename T>
class Box {
  T value_;
public: T get() const { return value_; }
};
容易踩坑:把类型参数命名清楚,错误信息会更容易读。
自测读法:先遮住代码口述它会做什么;再指出关键变量、边界条件和可能失效的对象。若说不清,回到上面的概念逐句追踪。

03 · 模板实参推断

推断会考虑引用、const、数组和函数指针的特殊规则。传值会发生数组到指针的退化,传引用可以保留更多类型信息。

template <typename T>
void inspect(const T& value);
int a[3]{}; inspect(a);
容易踩坑:需要固定类型时可以显式写 template;否则让推断工作通常更通用。
自测读法:先遮住代码口述它会做什么;再指出关键变量、边界条件和可能失效的对象。若说不清,回到上面的概念逐句追踪。

04 · 重载、特化与约束思维

普通函数、模板和不同模板参数之间会参与重载决议。完全特化可以为特殊类型提供版本,但不要用特化掩盖设计问题。

template <typename T>
void print(const T&);
template <> void print(const char* const&);
容易踩坑:先问模板需要哪些操作;“鸭子类型”要求这些操作存在且语义合理。
自测读法:先遮住代码口述它会做什么;再指出关键变量、边界条件和可能失效的对象。若说不清,回到上面的概念逐句追踪。

05 · 转发与可变参数模板

参数包可以接收任意数量参数,std::forward 保留参数原本的左值/右值属性,常用于工厂和包装函数。

template <typename... Args>
auto make(Args&&... args) {
  return Widget(std::forward<Args>(args)...);
}
容易踩坑:完美转发很强,也更难读;先用普通重载,确实需要通用包装时再使用。
自测读法:先遮住代码口述它会做什么;再指出关键变量、边界条件和可能失效的对象。若说不清,回到上面的概念逐句追踪。

06 · 小练习

实现一个模板函数 print_all,接收任意数量参数并用空格分隔输出;再让它支持一个 vector。

template <typename T, typename... Rest>
void print_all(const T& first, const Rest&... rest);
参考思路:递归展开时要准备终止分支,C++17 以后也可以用折叠表达式。
展开参考代码
template <typename T, typename... Rest>
void print_all(const T& first, const Rest&... rest);

07 · 模板的声明与定义位置

模板必须在实例化处看得到定义,通常把实现写在头文件。普通函数可以只在头文件声明,因为链接器之后再找定义。

// widget.h
 template <typename T> T max_value(T, T) { return ...; }
容易踩坑:把模板实现放进 cpp 会导致调用者实例化时找不到定义。
自测读法:先遮住代码口述它会做什么;再指出关键变量、边界条件和可能失效的对象。若说不清,回到上面的概念逐句追踪。

08 · 显式实例化与 extern template

显式实例化可以提前生成某个类型的模板,extern template 告诉其他文件不要重复生成,适合大型项目减少编译时间。

extern template class std::vector<int>;
template class Box<int>;
容易踩坑:优化编译时间前先测量,模板可见性优先保证正确。
自测读法:先遮住代码口述它会做什么;再指出关键变量、边界条件和可能失效的对象。若说不清,回到上面的概念逐句追踪。

09 · 模板参数的类型与非类型

模板参数可以是类型、整数、指针或引用等非类型参数。array 的大小就是典型非类型参数。

template <typename T, std::size_t N>
std::size_t count(const T (&)[N]) { return N; }
容易踩坑:非类型参数必须满足编译期常量要求。
自测读法:先遮住代码口述它会做什么;再指出关键变量、边界条件和可能失效的对象。若说不清,回到上面的概念逐句追踪。

10 · 尾置返回与 decltype

当返回类型依赖参数时,可以用 auto 加尾置 -> decltype 表达返回类型。decltype((x)) 可能得到引用,括号会改变规则。

template <typename It>
auto first(It it) -> decltype(*it) { return *it; }
容易踩坑:区分 decltype(name) 和 decltype((expression)),它们的结果可能不同。
自测读法:先遮住代码口述它会做什么;再指出关键变量、边界条件和可能失效的对象。若说不清,回到上面的概念逐句追踪。

11 · 模板特化

特化为某个具体类型提供不同实现;偏特化为一类类型提供实现,但函数模板不能偏特化,只能重载。

template <typename T> struct Traits;
template <> struct Traits<int> { using type = long; };
容易踩坑:特化应解决真正的语义差异,不要为每个小变化复制一份模板。
自测读法:先遮住代码口述它会做什么;再指出关键变量、边界条件和可能失效的对象。若说不清,回到上面的概念逐句追踪。
REVIEW

本章理解检查

先自己回答,再展开解释。

模板定义为什么通常放在头文件?

实例化发生在调用者所在翻译单元,编译器必须在那里看见模板定义。

完美转发中的 forward 做什么?

按模板推导结果保留实参原先是左值还是右值的属性。

模板实参推断会自动把两个不同类型都转成同一类型吗?

通常不会。若 T 同时从两个参数推断,结果冲突会失败;可显式指定类型或设计不同的模板参数。

类模板和函数模板的特化规则相同吗?

不完全相同。类模板支持偏特化;函数模板不能偏特化,通常用重载实现类似选择。
NEXT

继续学习

完成本章后,建议进入第 17 章;也可以回到首页查看知识地图或打开交互实验。