c/c++h中的多态(下)

c/c++h中的多态(下) 1.C11 override 和 final在 C11 中override和final是两个用于增强代码可读性和安全性的关键字主要用于类的继承体系中。1.override 关键字override用于显式标记派生类中覆写基类虚函数的成员函数。它的作用是确保派生类的函数确实覆盖了基类的虚函数避免因函数签名不匹配而导致的隐藏hiding而非覆盖overriding的问题。class Base { public: virtual void foo(int); virtual void bar() const; }; class Derived : public Base { public: void foo(int) override; // 正确覆盖 Base::foo(int) void bar() override; // 错误签名不匹配无法覆盖 Base::bar() const };如果派生类函数标记为override但未找到匹配的基类虚函数编译器会报错。避免因拼写错误或参数类型不一致导致意外隐藏基类函数。2.final 关键字final可以用于类或虚函数表示禁止进一步派生或覆盖。用于类class Base final { // Base 不能被继承 }; class Derived : public Base { // 错误Base 是 final 类 };用于虚函数class Base { public: virtual void foo() final; // 禁止派生类覆盖 foo }; class Derived : public Base { public: void foo(); // 错误无法覆盖 final 函数 };用于类时表示该类不能被继承。用于虚函数时表示该函数在派生类中不能被覆盖。3.使用场景对比关键字作用对象用途override派生类成员函数显式标记覆盖基类虚函数确保签名匹配避免隐藏问题。final类或虚函数禁止类被继承或禁止虚函数被进一步覆盖增强设计约束。class Shape { public: virtual void draw() const 0; virtual ~Shape() default; }; class Circle final : public Shape { // Circle 不能被继承 public: void draw() const override; // 明确覆盖基类纯虚函数 }; class Ellipse : public Circle { // 错误Circle 是 final 类 };override和final显著提升了代码的可维护性和安全性。现代 C 开发中推荐优先使用这两个关键字。2.虚函数表#include iostream using namespace std; class Base { public: virtual void Func1() { cout Func1() endl; } private: int _b 1; }; int main() { cout sizeof(Base): sizeof(Base) endl; return 0; }这个结果会是多少呢运行结果不同的编译器结果也可能会不同不知道小伙伴们还记不记得对齐规则1. 单个成员变量的对齐倍数 自身类型大小 和 编译器默认最大对齐数 中 较小的值2. 整个结构体 / 类的总大小对齐倍数 结构体 / 类中 最大的有效对齐数最终总大小必须是这个数的整数倍. 编译器「默认最大对齐数」Windows (VS/MSVC)默认 8 字节Linux (GCC)默认 4 字节这个题在windows下运行就是16字节Linux就是12字节。大家此时就会疑惑这个类明明只有一个整型的变量大小应该是4字节才对吧。只要类里有虚函数编译器就会自动生成两个东西1.虚函数表vtable属于类所有同类对象共享一张本质是个数组里面存的是该类所有虚函数的内存地址。2.虚函数指针vptr属于对象每个对象自带一个是个指针永远指向自己类的虚函数表。我们用 Animal(基类) → Dog(派生类) 举例class Animal { public: virtual void speak() { cout 动物叫 endl; } };编译器自动为 Animal 生成虚函数表Animal 虚函数表地址speak()Animal::speak创建 Animal 对象时对象自带 vptr指向这张表。class Dog : public Animal { public: void speak() override { cout 汪汪汪 endl; } };编译器自动为 Dog 生成新的虚函数表没重写的虚函数直接继承基类的地址重写的虚函数替换成自己的函数地址。Dog 虚函数表地址speak()Dog::speak创建 Dog 对象时对象的 vptr 指向Dog 自己的虚表。其实在这个时候大家就能知道为啥运行结果不是4了。平台64 位32 位Linux (GCC)128Windows (VS)168因为虚函数有指针在64位系统大小为8字节在32位系统就为4字节。2.1单继承和多继承关系的虚函数表1.单继承的虚函数表核心特点1.基类有虚表 → 派生类直接继承这张表2.派生类重写基类虚函数替换虚表中对应位置的地址3.派生类新增虚函数追加到虚表的末尾4.全程只有 1 个虚指针vptr指向这 1 张虚表// 基类 class Base { public: virtual void Func1() { cout Base::Func1 endl; } virtual void Func2() { cout Base::Func2 endl; } }; // 派生类单继承 class Derive : public Base { public: // 重写基类虚函数 void Func1() override { cout Derive::Func1 endl; } // 新增自己的虚函数 virtual void Func3() { cout Derive::Func3 endl; } };2.多继承的虚函数表核心特点:1.继承了 N 个基类 → 生成 N 张独立的虚函数表2.派生类对象会包含 N 个虚指针vptr每个基类子对象自带一个3.重写哪个基类的虚函数 → 就替换对应那张虚表的地址4.派生类新增的虚函数 → 只追加到第一张虚表第一个基类的虚表// 基类1 class Base1 { public: virtual void Func1() { cout Base1::Func1 endl; } }; // 基类2 class Base2 { public: virtual void Func2() { cout Base2::Func2 endl; } }; // 派生类多继承继承2个基类 class Derive : public Base1, public Base2 { public: // 重写 Base1 的虚函数 void Func1() override { cout Derive::Func1 endl; } // 重写 Base2 的虚函数 void Func2() override { cout Derive::Func2 endl; } // 新增虚函数 virtual void Func3() { cout Derive::Func3 endl; } };3.对比特性单继承多继承虚函数表数量1 张N 张N 基类个数虚指针数量1 个N 个重写虚函数替换唯一虚表替换对应基类的虚表新增虚函数追加到表尾仅追加到第一张虚表3.组合和继承1. 继承 is-a是一个白盒复用子类完全继承父类的属性和方法例子狗 是 动物、学生 是 人语法class 狗 : public 动物2. 组合 has-a有一个黑盒复用一个类包含另一个类的对象例子汽车 有 引擎、电脑 有 CPU语法class 汽车 { 引擎 m_engine; };继承is-aclass Animal { void speak() {} }; // 狗 是 动物 → 继承 class Dog : public Animal { };组合has-aclass Engine { void start() {} }; // 汽车 有 引擎 → 组合 class Car { Engine engine; // 把其他类当成员变量 };核心区别特性继承 (Inheritance)组合 (Composition)关系is-a是一个has-a有一个耦合度高耦合牵一发而动全身低耦合灵活独立封装性破坏封装白盒复用保护封装黑盒复用灵活性差编译期固定强运行时可替换坑点菱形继承、多继承复杂无复杂问题设计原则慎用优先使用为什么「优先组合慎用继承」1.继承耦合太高父类代码一改所有子类都可能报错维护成本极高。2.组合更灵活想换零件就换零件比如汽车换引擎不影响整体结构。3.组合不破坏封装只调用外部接口看不到内部代码更安全。4.继承滥用会导致类爆炸层级太深代码完全无法维护。谢谢