cs106l-lecture-5-5-inheritance-namespaces
Course: 斯坦福大学: C++标准库编程 | C++ Standard Library Programming
🎮 CS106L Lecture 5.5 — Inheritance & Namespaces
🏛 课程:Stanford CS106L — Standard C++ Programming Language
📍 主题:命名空间与继承(Namespaces & Inheritance)
🎯 目标:理解命名空间用于作用域隔离的机制,以及继承如何支持多态与代码复用。
🧭 课程定位
本讲收尾了 CS106L 的面向对象基础模块。
在前几讲我们学习了:
- 类与构造函数
- 拷贝 / 移动语义
- 运算符重载
现在我们进入两大核心抽象工具:
“命名空间(Namespaces)管理复杂系统的名字;
继承(Inheritance)让类形成可扩展层次结构。”
🏷 关键词速记
Namespace|命名空间
Scope Resolution|作用域解析符
Inheritance|继承
Virtual Function|虚函数
Abstract Class|抽象类
Interface|接口
Polymorphism|多态
Access Specifier|访问控制
🎮 30 秒课程摘要
“命名空间解决‘谁是谁’的问题;
继承解决‘谁能干什么’的问题。”
📚 核心内容讲解
🧩 1️⃣ 命名空间(Namespace)
命名空间用于防止命名冲突,在大型项目中尤其重要。
📦 示例 1:基本命名空间定义
#include <iostream>
namespace math {
int add(int a, int b) { return a + b; }
}
int main() {
std::cout << math::add(2, 3); // ✅ 使用作用域解析符
}📤 输出:
5
🧠 讲解:
namespace创建逻辑作用域。::作用域解析符访问命名空间内成员。
🧩 2️⃣ 嵌套与别名命名空间
📦 示例 2:嵌套命名空间
namespace stanford {
namespace cs106l {
void lecture() {
std::cout << "Welcome to CS106L!\n";
}
}
}
int main() {
stanford::cs106l::lecture();
}📦 简写(C++17 新语法):
namespace stanford::cs106l {
void lecture() { std::cout << "Welcome!\n"; }
}🧩 命名空间别名:
namespace lec = stanford::cs106l;
lec::lecture();🧠 建议:
多层命名空间应使用别名以提高可读性。
🧩 3️⃣ using 与 std:: 风格
📦 示例 3:不同使用方式
using namespace std; // ⚠️ 不推荐:污染全局命名空间
using std::cout; // ✅ 推荐:显式导入
using std::string;🧠 最佳实践:
| 场景 | 推荐做法 |
|---|---|
| 头文件 | ❌ 禁止使用 using namespace |
| 源文件 | ✅ 可以使用局部 using |
| 局部作用域 | ✅ using std::cout; 明确导入 |
“永远不要在 .h 文件中写 using namespace std;。”
🧩 4️⃣ 继承基础(Inheritance Basics)
继承允许一个类复用另一个类的属性与方法。
📦 示例 4:基本继承
class Animal {
public:
void speak() const { std::cout << "Some sound\n"; }
};
class Dog : public Animal {
public:
void bark() const { std::cout << "Woof!\n"; }
};
int main() {
Dog d;
d.speak(); // ✅ 继承 Animal 的方法
d.bark();
}🧠 讲解:
Dog是Animal的子类(derived class)Animal是基类(base class)public表示公有继承,保留基类接口
🧩 5️⃣ 访问控制(Access Specifiers)
| 关键字 | 子类访问 | 外部访问 |
|---|---|---|
public | ✅ | ✅ |
protected | ✅ | ❌ |
private | ❌ | ❌ |
📦 示例 5:protected 示例
class Base {
protected:
int value;
};
class Derived : public Base {
public:
void set(int v) { value = v; } // ✅ 允许访问 protected 成员
};🧠 建议:
“优先使用 private,只在确有继承需求时使用 protected。”
🧩 6️⃣ 构造与析构顺序
📦 示例 6:执行顺序
class Base {
public:
Base() { std::cout << "Base ctor\n"; }
~Base() { std::cout << "Base dtor\n"; }
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived ctor\n"; }
~Derived() { std::cout << "Derived dtor\n"; }
};
int main() {
Derived d;
}📤 输出:
Base ctor
Derived ctor
Derived dtor
Base dtor
🧠 口诀:
“构造由上到下,析构由下到上。”
🧩 7️⃣ 虚函数(Virtual Functions)
虚函数允许子类重写基类行为,实现多态(Polymorphism)。
📦 示例 7:虚函数实现
class Animal {
public:
virtual void speak() const {
std::cout << "Animal sound\n";
}
};
class Dog : public Animal {
public:
void speak() const override {
std::cout << "Woof!\n";
}
};
void makeSound(const Animal& a) {
a.speak(); // ✅ 动态绑定
}
int main() {
Dog d;
makeSound(d); // 输出 Woof!
}🧠 讲解:
virtual→ 运行时绑定(动态多态)override→ 防止拼写错误或签名不匹配- 若不使用虚函数,将触发静态绑定(编译期)
🧩 8️⃣ 抽象类与接口
📦 示例 8:抽象类
class Shape {
public:
virtual void draw() const = 0; // 纯虚函数
};
class Circle : public Shape {
public:
void draw() const override {
std::cout << "Drawing Circle\n";
}
};📦 使用:
Shape* s = new Circle();
s->draw(); // ✅ 动态多态
delete s;🧠 讲解:
= 0表示纯虚函数(pure virtual function)- 抽象类不能被实例化
- 子类必须实现所有纯虚函数
🧩 9️⃣ 析构函数的多态陷阱
若基类含虚函数,必须也让析构函数是虚的。
📦 错误示例:
class Base { ~Base() { std::cout << "Base destroyed\n"; } };
class Derived : public Base { ~Derived() { std::cout << "Derived destroyed\n"; } };
Base* p = new Derived();
delete p; // ❌ 未定义行为
📦 修复:
class Base {
public:
virtual ~Base() { std::cout << "Base destroyed\n"; }
};🧠 原则:
“有虚函数的类,析构函数也要是虚函数。”
🧩 🔟 继承与模板的对比
| 特性 | 继承 | 模板 |
|---|---|---|
| 绑定时间 | 运行时(多态) | 编译期(泛型) |
| 主要用途 | 行为扩展 | 类型泛化 |
| 语法复杂度 | 高 | 中 |
| 性能 | 稍慢(虚表开销) | 零开销抽象 |
| 示例 | virtual draw() | template<typename T> |
🧠 结论:
“继承解决行为差异,模板解决类型差异。”
🧠 记忆口诀
“命名防冲突,继承求共性;
虚函数显多态,析构虚以静。”
🧪 实践练习
✅ 练习 1:自定义命名空间
创建 namespace cs106l { ... } 并实现函数 hello()。
✅ 练习 2:继承层次设计
实现 Shape → Circle → ColoredCircle 层次结构,并演示多态。
✅ 练习 3:析构顺序测试
为基类与子类添加打印语句,观察析构顺序。
✅ 练习 4:纯虚函数接口
设计 IPlayable 接口类并实现 AudioPlayer 与 VideoPlayer。
🪄 工程与设计建议
- 在头文件中绝不写
using namespace std; - 通过命名空间区分不同模块逻辑(如
core::,utils::) - 有虚函数的类 → 析构函数必须是
virtual - 若类需要多态访问,应使用指针或引用调用接口函数
- 抽象类命名建议以
I开头(如IShape,IReader) - 若不需要继承,使用
final防止误继承 - 模板优先于继承,用于编译期泛化逻辑
✅ 小结表
| 概念 | 定义 | 示例 |
|---|---|---|
| 命名空间 | 作用域隔离机制 | namespace cs106l {} |
| 作用域解析符 | 访问命名空间成员 | math::add() |
| 继承 | 类间行为复用 | class Dog : public Animal |
| 虚函数 | 运行时多态 | virtual draw() |
| 纯虚函数 | 抽象接口 | virtual void f() = 0; |
| 虚析构函数 | 安全销毁基类指针 | virtual ~Base() |
“命名空间让世界井然有序,
继承让思想层次分明。”