cs106l-lecture-5-6-inheritance-and-template-clas
Course: 斯坦福大学: C++标准库编程 | C++ Standard Library Programming
🎮 CS106L Lecture 5.6 — Inheritance and Template Classes
🏛 课程:Stanford CS106L — Standard C++ Programming Language
📍 主题:继承与模板类(Inheritance & Template Classes)
🎯 目标:理解**继承(动态多态)与模板(静态多态)**的本质差异,并学习如何定义类模板,实现可扩展的泛型类型系统。
🧭 课程定位
这节课是 CS106L 面向对象 (OOP) 向 泛型编程 (Generic Programming) 的过渡讲。
从“类层次继承”走向“模板类型系统”,
C++ 的两种核心抽象方式在此汇合。
💡 “继承让对象共享行为;模板让行为适配类型。”
🏷 关键词速记
Inheritance|继承
Dynamic Polymorphism|动态多态
Template Class|模板类
Compile-time Polymorphism|静态多态
Concepts|类型约束(C++20)
Pure Virtual Function|纯虚函数
Virtual Destructor|虚析构
Template Parameter|模板参数
🎮 30 秒课程摘要
“继承让你在运行时决定做什么;
模板让你在编译时决定怎么做。”
📚 核心内容讲解
🧩 1️⃣ 抽象类与纯虚函数复习
📦 示例 1:抽象基类
class Shape {
public:
virtual void draw() const = 0; // 纯虚函数
virtual ~Shape() = default; // 虚析构,防止内存泄漏
};📦 子类实现:
class Circle : public Shape {
public:
void draw() const override {
std::cout << "Drawing Circle\n";
}
};📦 多态调用:
void render(const Shape& s) {
s.draw(); // 动态绑定:运行时决定调用哪个函数
}🧠 总结:
= 0表示纯虚函数,类成为抽象类(不能实例化)- 通过基类指针或引用实现 运行时多态
- 析构函数必须是
virtual,否则销毁基类指针会触发 UB
“继承是一种运行时契约。”
🧩 2️⃣ 构造与析构顺序
📦 示例 2:构造 / 析构执行顺序
class Base {
public:
Base() { std::cout << "Base ctor\n"; }
virtual ~Base() { std::cout << "Base dtor\n"; }
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived ctor\n"; }
~Derived() { std::cout << "Derived dtor\n"; }
};📤 输出:
Base ctor
Derived ctor
Derived dtor
Base dtor
🧠 口诀:
“构造由上到下,析构由下到上。”
🧩 3️⃣ 动态多态 vs 静态多态
| 特性 | 动态多态(继承) | 静态多态(模板) |
|---|---|---|
| 绑定时间 | 运行时 (runtime) | 编译期 (compile-time) |
| 实现机制 | 虚表 (vtable) | 模板实例化 |
| 性能 | 稍慢 | 零开销 |
| 灵活性 | 支持运行时替换 | 类型固定但快速 |
| 示例 | virtual draw() | template<typename T> |
📦 示例 3:两种多态
// 动态多态
Shape* s = new Circle();
s->draw(); // 运行时决定
// 静态多态
template<typename T>
void draw(T& shape) { shape.draw(); } // 编译时决定
🧠 结论:
“继承适合行为差异化;模板适合类型参数化。”
🧩 4️⃣ 模板类基础语法
📦 示例 4:类模板定义
template<typename T>
class Box {
public:
explicit Box(const T& value) : data(value) {}
const T& get() const { return data; }
private:
T data;
};📦 使用示例
Box<int> b1(42);
Box<std::string> b2("CS106L");
std::cout << b1.get() << " " << b2.get() << "\n";📤 输出:
42 CS106L
🧠 讲解:
- 模板类是一种“类型工厂”。
- 编译时根据传入的
T自动生成具体类。 - 模板的函数和类定义通常写在头文件中(模板需可见定义)。
🧩 5️⃣ 模板类与继承结合
📦 示例 5:继承模板类
template<typename T>
class Base {
public:
void print() const { std::cout << "Base: " << value << "\n"; }
protected:
T value = {};
};
template<typename T>
class Derived : public Base<T> {
public:
void set(const T& val) { this->value = val; }
};📦 使用:
Derived<int> d;
d.set(100);
d.print(); // 输出 Base: 100
🧠 注意:
- 子类访问基类模板成员时需使用
this->或Base<T>::显式指明作用域。
🧩 6️⃣ 模板参数默认值
📦 示例 6:模板类默认参数
template<typename T, typename Container = std::vector<T>>
class Stack {
public:
void push(const T& item) { data.push_back(item); }
void pop() { data.pop_back(); }
const T& top() const { return data.back(); }
private:
Container data;
};📦 使用:
Stack<int> s1; // 默认 std::vector<int>
Stack<int, std::deque<int>> s2; // 自定义容器类型
🧠 讲解:
- 模板参数可设置默认类型。
- 支持多种底层容器结构的“策略模式”。
🧩 7️⃣ 泛型优先队列(PriorityQueue)示例
📦 示例 7:模板类实现
template<typename T, typename Compare = std::less<T>>
class PriorityQueue {
public:
void push(const T& item) {
data.push_back(item);
std::sort(data.begin(), data.end(), comp);
}
const T& top() const { return data.back(); }
void pop() { data.pop_back(); }
bool empty() const { return data.empty(); }
private:
std::vector<T> data;
Compare comp;
};📦 使用示例
PriorityQueue<int> pq;
pq.push(3);
pq.push(1);
pq.push(5);
std::cout << pq.top(); // 输出 5
🧠 讲解:
- 通过模板参数传入比较器,实现通用排序逻辑。
- 默认比较函数为
std::less<T>,行为与std::priority_queue一致。
🧩 8️⃣ 模板与继承的融合哲学
📦 示例 8:静态 vs 动态复用
// 动态多态
class Animal { public: virtual void speak() const = 0; };
class Dog : public Animal { public: void speak() const override { std::cout << "Woof\n"; } };
// 静态多态(模板版)
template<typename T>
void speak(const T& animal) {
animal.speak(); // 编译期多态
}🧠 讲解:
- 继承:关注对象“能做什么”(行为共性)
- 模板:关注类型“是什么”(泛型约束)
“继承偏向抽象接口;模板偏向抽象类型。”
🧩 9️⃣ C++20 Concepts 与约束模板
📦 示例 9:Concepts 示例
#include <concepts>
template<typename T>
concept Comparable = requires(T a, T b) {
{ a < b } -> std::convertible_to<bool>;
};
template<Comparable T>
bool smaller(const T& a, const T& b) {
return a < b;
}🧠 讲解:
concept限制模板参数类型的可用操作。- 提升可读性与编译期错误诊断。
📦 调用:
smaller(3, 5); // ✅ OK
// smaller("a", 1); // ❌ 编译时报错:类型不满足 Comparable
🧠 记忆口诀
“继承让行为复用,模板让类型泛化;
动态决定谁干活,静态决定谁能干。”
🧪 实践练习
✅ 练习 1:抽象类继承
实现 Shape 抽象类与两个子类 Circle, Square,并通过多态绘制图形。
✅ 练习 2:模板类 Stack
实现 Stack<T> 模板类,支持 push/pop/top 并使用不同底层容器。
✅ 练习 3:比较器模板参数
为 PriorityQueue 添加自定义排序函数模板参数。
✅ 练习 4:Concepts 限制
定义 concept Printable 并编写仅接受支持 << 的类型的函数模板。
🪄 工程与设计建议
- 抽象类中必须有
virtual ~Base(),否则多态删除会出错。 - 若类不打算继承,应声明为
final。 - 模板类定义通常应放在
.h文件中(编译期实例化需要可见定义)。 - 避免模板函数重复定义冲突(可通过命名空间隔离)。
- 优先使用 Concepts 代替 SFINAE,提升编译错误可读性。
- 模板 + Lambda 可替代部分传统继承结构。
✅ 小结表
| 概念 | 本质 | 示例 | 阶段 |
|---|---|---|---|
| 抽象类 | 行为抽象 | virtual draw() = 0 | 运行时 |
| 虚函数 | 动态多态 | speak() | 运行时 |
| 模板类 | 类型抽象 | template<typename T> | 编译期 |
| 模板参数 | 泛型机制 | <T, Compare> | 编译期 |
| Concepts | 约束模板类型 | requires Comparable | 编译期 |
“继承让你定义‘共性’,模板让你提炼‘模式’。
二者结合,是 C++ 的抽象之美。”