764 字
2 分钟
【CPP】继承与多态
类之间的关系
| 关系 | 英文 | 含义 |
|---|---|---|
| 继承 | is-a | 派生类是一种基类 |
| 组合/聚合 | has-a | 整体包含部分 |
| 依赖/关联 | uses-a | 一个类使用另一个类 |
| 实现 | implements | 类实现接口(抽象类/纯虚) |
另有 instance-of:对象是类的实例。
Liskov 替换原则 (LSP)
子类型必须能够替换其基类型,且程序行为不变。
经典反例:从 Rectangle 派生 Square,若基类允许独立修改宽和高,正方形约束被破坏,行为上不能替换矩形。
实践:
- 继承应基于可替换的行为,不仅是分类直觉。
- 子类扩展基类,不要破坏基类契约。
继承方式与访问
class Base {public: int pub;protected: int prot;private: int priv;};
class D_pub : public Base {}; // is-a,最常用class D_prot : protected Base {};class D_priv : private Base {};| 继承方式 | 基类 public | 基类 protected | 基类 private |
|---|---|---|---|
| public | public | protected | 不可访问 |
| protected | protected | protected | 不可访问 |
| private | private | private | 不可访问 |
只有 public 继承才保持典型的 is-a 语义,适合多态。
成员继承与构造/析构
| 成员/函数 | 是否继承 | 说明 |
|---|---|---|
| 普通成员函数 | ✓ | 子类同名函数隐藏基类所有重载(可用 using Base::f 引入) |
| 虚函数 | ✓ | 可 override(C++11 建议显式写 override) |
| 静态成员函数 | ✓ | 子类同名同样隐藏基类同名 |
| 构造函数 | ✗ | 子类须在初始化列表调用基类构造 |
| 析构函数 | 部分 | 子类析构自动调用基类析构 |
| 友元 | ✗ | 友元不传递 |
注意:若基类没有默认构造且子类未在初始化列表中调用基类合适构造,子类的默认构造会被 = delete。
class Base { public: Base(int) {} };class Derived : public Base {public: Derived() : Base(0) {} // 必须显式调用};C++11:using Base::Base; 可继承构造函数。
多态与虚函数
编译期多态:函数重载、模板。
运行期多态:通过基类指针/引用调用 virtual 函数,根据对象实际类型分派。
struct Animal { virtual void speak() const { std::cout << "?\n"; } virtual ~Animal() = default; // 多态基类必备};struct Dog : Animal { void speak() const override { std::cout << "woof\n"; }};
void call(const Animal& a) { a.speak(); } // 运行时决定实现上,含虚函数的类常有 虚表指针 (vptr) 指向虚函数表;因此对象大小可能多一个指针。
纯虚函数与抽象类
class Shape {public: virtual double area() const = 0; // 纯虚 virtual ~Shape() = default;};// Shape s; // 错误:不能实例化抽象类class Circle : public Shape {public: double area() const override { return 3.14 * r * r; }private: double r;};- 含至少一个纯虚函数的类为抽象类。
- 派生类必须实现所有纯虚函数才能实例化(除非自己也成为抽象类)。
- 接口类:抽象类中全部为纯虚函数。
override 与 final
void f() override; // 编译期检查是否真的覆盖void g() final; // 禁止进一步 override多继承与菱形继承
class A { public: int x; };class B : public A {};class C : public A {};class D : public B, public C {}; // D 中有两份 A::x问题:D 对象含两份 A 子对象 → 二义性、浪费。
解决:虚继承
class B : virtual public A {};class C : virtual public A {};class D : public B, public C {}; // 仅一份 A虚继承增加实现复杂度,应谨慎使用;优先组合往往更清晰。
指针/引用的转换
| 转换 | 方向 | 安全性 |
|---|---|---|
| 上行 | 派生 → 基类 | 安全(隐式) |
| 下行 | 基类 → 派生 | 不安全,需 dynamic_cast 等 |
Dog d;Animal* pa = &d; // OK
Animal* pa = /* ... */;Dog* pd = dynamic_cast<Dog*>(pa); // 失败返回 nullptr(指针形式)重写 vs 重载(原笔记疑问)
| 重载 (overload) | 重写/覆盖 (override) | |
|---|---|---|
| 作用域 | 同一作用域 | 基类与派生类 |
| 函数签名 | 参数不同 | 参数相同(协变返回类型例外) |
| 绑定 | 编译期 | 运行期(虚函数) |
| 关键字 | 无 | virtual / override |
分享
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时
相关文章 智能推荐
