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|访问控制


“命名空间解决‘谁是谁’的问题;

继承解决‘谁能干什么’的问题。”



命名空间用于防止命名冲突,在大型项目中尤其重要。

📦 示例 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:嵌套命名空间

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 namespace std;      // ⚠️ 不推荐:污染全局命名空间
using std::cout;          // ✅ 推荐:显式导入
using std::string;

🧠 最佳实践:

场景推荐做法
头文件❌ 禁止使用 using namespace
源文件✅ 可以使用局部 using
局部作用域using std::cout; 明确导入

“永远不要在 .h 文件中写 using namespace std;。”


继承允许一个类复用另一个类的属性与方法。

📦 示例 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();
}

🧠 讲解:

  • DogAnimal 的子类(derived class)
  • Animal 是基类(base class)
  • public 表示公有继承,保留基类接口

关键字子类访问外部访问
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:执行顺序

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

🧠 口诀:

“构造由上到下,析构由下到上。”


虚函数允许子类重写基类行为,实现多态(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:抽象类

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)
  • 抽象类不能被实例化
  • 子类必须实现所有纯虚函数

若基类含虚函数,必须也让析构函数是虚的。

📦 错误示例:

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:继承层次设计

实现 ShapeCircleColoredCircle 层次结构,并演示多态。

练习 3:析构顺序测试

为基类与子类添加打印语句,观察析构顺序。

练习 4:纯虚函数接口

设计 IPlayable 接口类并实现 AudioPlayerVideoPlayer


  • 在头文件中绝不写 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()

“命名空间让世界井然有序,

继承让思想层次分明。”