语言地基
从输入输出、类型、表达式到函数,把“能编译”变成能解释。
这不是章节目录,而是一套循序渐进的学习课程。每个知识点都按“为什么需要它、代码怎样运行、哪里最容易错、如何练习”展开。
#include <iostream>
int main() {
int total = 0;
for (int n : {3, 5, 7}) total += n;
std::cout << total << '\\n';
}
每章都可以单独阅读,也能沿推荐顺序继续。
从输入输出、类型、表达式到函数,把“能编译”变成能解释。
用 string、vector、迭代器和算法解决真实的数据处理问题。
从拷贝控制到继承,把对象的生命周期和接口边界讲清楚。
模板、tuple、正则、随机数和异常处理,连接到大型程序。
用 20 分钟走完一个小闭环。
先把“值、类型、对象、作用域”分开,再看 auto、const 和引用如何改变表达方式。
同一个字节可以是整数、字符或布尔值。
用一张状态图追踪赋值和修改。
改变一个输入,观察 size 和 capacity 的关系。它是教学模型,用来建立预测,而不是模拟某个特定实现。
答案可展开,进度不会计分。
同一个 vector 为什么 size 和 capacity 不一定相等?
引用和指针最关键的区别是什么?
为什么基类析构函数常常要写 virtual?
每一部分解决一个更大的问题。
先理解,再把这里当作查阅工具。
T:对象自己拥有资源;unique_ptr:唯一拥有;shared_ptr:共享拥有;weak_ptr:只观察不拥有。
T:小对象复制;const T&:只读大对象;T&:必须修改;T*:可能为空或需要改指向。
vector:默认序列;map:有序键值;unordered_map:哈希查找;set:唯一键集合。
模板描述一套适用于多种类型的代码。编译器在调用处推断 T,再实例化具体版本;错误往往出现在实例化阶段。
template <typename T>
const T& larger(const T& a, const T& b) {
return a < b ? b : a;
}类模板可以把类型作为参数,例如 vector
template <typename T>
class Box {
T value_;
public: T get() const { return value_; }
};推断会考虑引用、const、数组和函数指针的特殊规则。传值会发生数组到指针的退化,传引用可以保留更多类型信息。
template <typename T>
void inspect(const T& value);
int a[3]{}; inspect(a);普通函数、模板和不同模板参数之间会参与重载决议。完全特化可以为特殊类型提供版本,但不要用特化掩盖设计问题。
template <typename T>
void print(const T&);
template <> void print(const char* const&);参数包可以接收任意数量参数,std::forward 保留参数原本的左值/右值属性,常用于工厂和包装函数。
template <typename... Args>
auto make(Args&&... args) {
return Widget(std::forward<Args>(args)...);
}实现一个模板函数 print_all,接收任意数量参数并用空格分隔输出;再让它支持一个 vector。
template <typename T, typename... Rest>
void print_all(const T& first, const Rest&... rest);template <typename T, typename... Rest>
void print_all(const T& first, const Rest&... rest);模板必须在实例化处看得到定义,通常把实现写在头文件。普通函数可以只在头文件声明,因为链接器之后再找定义。
// widget.h
template <typename T> T max_value(T, T) { return ...; }显式实例化可以提前生成某个类型的模板,extern template 告诉其他文件不要重复生成,适合大型项目减少编译时间。
extern template class std::vector<int>;
template class Box<int>;模板参数可以是类型、整数、指针或引用等非类型参数。array 的大小就是典型非类型参数。
template <typename T, std::size_t N>
std::size_t count(const T (&)[N]) { return N; }当返回类型依赖参数时,可以用 auto 加尾置 -> decltype 表达返回类型。decltype((x)) 可能得到引用,括号会改变规则。
template <typename It>
auto first(It it) -> decltype(*it) { return *it; }特化为某个具体类型提供不同实现;偏特化为一类类型提供实现,但函数模板不能偏特化,只能重载。
template <typename T> struct Traits;
template <> struct Traits<int> { using type = long; };先自己回答,再展开解释。
模板定义为什么通常放在头文件?
完美转发中的 forward 做什么?
模板实参推断会自动把两个不同类型都转成同一类型吗?
类模板和函数模板的特化规则相同吗?
完成本章后,建议进入第 17 章;也可以回到首页查看知识地图或打开交互实验。