当前位置: 首页 > news >正文

C++ 多态:从概念到实践,吃透面向对象核心特性

在面向对象编程(OOP)中,多态是三大核心特性(封装、继承、多态)的压轴戏。它解决了 “同一行为在不同对象上产生不同结果” 的问题,是代码解耦、提升扩展性的关键。本文将从通俗概念入手,逐步深入多态的实现细节、原理机制,最后结合实际场景带你落地实践,帮你彻底掌握 C++ 多态。

一、什么是多态?—— 通俗理解

多态的核心是 “一个接口,多种实现”。通俗来说:完成同一个行为,不同对象去做会产生不同的效果

举两个生活中的例子,帮你快速 get:

  1. 买票行为
  • 普通人买票 → 全价;

  • 学生买票 → 半价;

  • 军人买票 → 优先通道。

    同样是 “买票”,不同身份的人(对象)执行,结果不同。

  1. 支付宝扫红包
  • 新用户扫码 → 红包金额大(8-10 元);

  • 老用户扫码 → 红包金额小(0.1-0.5 元)。

    同样是 “扫码”,不同用户(对象)执行,红包结果不同。

在 C++ 中,多态的官方定义是:在不同继承关系的类对象上,调用同一函数,产生不同行为。比如Student继承PersonPerson对象调用BuyTicket()是全价,Student对象调用则是半价。

二、多态的定义与实现 —— 关键条件 + 代码示例

要实现多态,必须满足两个核心条件,缺一不可。我们先拆解条件,再通过代码落地。

2.1 多态的两个核心条件

  1. 调用方式:必须通过基类的指针或引用调用虚函数;

  2. 函数要求:被调用的函数必须是虚函数,且派生类必须重写基类的虚函数。

2.2 关键概念:虚函数与重写

在讲代码前,先明确两个基础概念:

1. 虚函数(virtual function)

virtual关键字修饰的类成员函数,就是虚函数。它是多态的 “开关”。

class Person {
public:// 虚函数:买票virtual void BuyTicket() {cout << "Person: 买票-全价" << endl;}
};
2. 虚函数的重写(覆盖)

派生类中定义一个与基类完全相同的虚函数(返回值、函数名、参数列表完全一致),称为 “重写”(也叫 “覆盖”)。

⚠️ 注意:派生类虚函数可省略virtual(因继承后基类虚函数属性保留),但不规范,建议显式加virtual,或用 C++11 的override关键字强制检查重写。

2.3 多态实现代码示例

下面用 “买票” 场景实现多态,代码带详细注释:

#include <iostream>
using namespace std;// 基类:Person
class Person {
public:// 1. 定义虚函数virtual void BuyTicket() {cout << "Person: 买票-全价" << endl;}
};// 派生类:Student(继承Person)
class Student : public Person {
public:// 2. 重写基类虚函数(用override确保重写,编译期检查错误)virtual void BuyTicket() override {cout << "Student: 买票-半价" << endl;}
};// 派生类:Soldier(继承Person)
class Soldier : public Person {
public:// 3. 重写基类虚函数virtual void BuyTicket() override {cout << "Soldier: 买票-优先通道" << endl;}
};// 4. 通过基类引用调用虚函数(满足多态条件1)
void DoBuyTicket(Person& people) {people.BuyTicket(); // 多态调用:根据实际对象类型决定执行哪个函数
}int main() {Person p;     // 基类对象Student s;    // 派生类1对象Soldier sol;  // 派生类2对象DoBuyTicket(p);   // 输出:Person: 买票-全价DoBuyTicket(s);   // 输出:Student: 买票-半价DoBuyTicket(sol); // 输出:Soldier: 买票-优先通道return 0;
}

运行结果

Person: 买票-全价Student: 买票-半价Soldier: 买票-优先通道

可见:同一函数DoBuyTicket,传入不同对象,执行结果完全不同 —— 这就是多态!

2.4 重写的两个例外情况

严格来说,重写要求 “返回值、函数名、参数列表完全一致”,但有两个合法例外:

例外 1:协变(返回值不同,但符合规则)

基类虚函数返回基类对象的指针 / 引用,派生类虚函数返回派生类对象的指针 / 引用,称为 “协变”,仍算重写。

class A {};
class B : public A {}; // B继承Aclass Person {
public:// 基类虚函数:返回A*virtual A* CreateObj() {cout << "Create A" << endl;return new A();}
};class Student : public Person {
public:// 派生类虚函数:返回B*(协变,算重写)virtual B* CreateObj() override {cout << "Create B" << endl;return new B();}
};
例外 2:析构函数的重写(函数名不同)

基类析构函数若为虚函数,派生类析构函数无论是否加virtual,都算重写。

原因:编译器会将所有析构函数统一处理为destructor(隐藏的统一函数名)。

为什么要重写析构函数?

避免内存泄漏!当用基类指针指向派生类对象时,若基类析构不是虚函数,delete时只会调用基类析构,派生类成员内存无法释放。

class Person {
public:virtual ~Person() { // 基类析构设为虚函数cout << "~Person()" << endl;}
};class Student : public Person {
public:~Student() override { // 重写析构cout << "~Student()" << endl;}
};int main() {Person* p1 = new Person();Person* p2 = new Student(); // 基类指针指向派生类对象delete p1; // 输出:~Person()delete p2; // 输出:~Student() → ~Person()(先析构派生类,再析构基类)return 0;
}

2.5 C++11:override 与 final 关键字

重写要求严格,若不小心写错函数名(如BuyTicket写成BuyTicker),编译器不会报错,只会导致多态失效,debug 困难。C++11 提供两个关键字解决这个问题:

1. override:强制检查重写

加在派生类虚函数后,若该函数未重写基类虚函数,编译期报错

class Student : public Person {
public:// 错误示例:函数名写错(BuyTicker≠BuyTicket),编译器直接报错virtual void BuyTicker() override { cout << "Student: 买票-半价" << endl;}
};
2. final:禁止虚函数被重写

加在基类虚函数后,该函数不能被任何派生类重写,否则编译报错。

class Person {
public:// final:禁止派生类重写BuyTicketvirtual void BuyTicket() final {cout << "Person: 买票-全价" << endl;}
};class Student : public Person {
public:// 错误:无法重写被final修饰的虚函数virtual void BuyTicket() override {cout << "Student: 买票-半价" << endl;}
};

2.6 易混淆概念:重载、重写、重定义(隐藏)

很多人会把这三个概念搞混,用一张表清晰对比:

特性重载(Overload)重写(Override)重定义(隐藏,Redefine)
作用域同一类基类与派生类基类与派生类
函数名相同相同相同
参数列表必须不同必须相同可相同可不同
返回值可相同可不同相同(协变例外)可相同可不同
虚函数无要求基类必须是虚函数,派生类重写无要求(若基类是虚函数且参数相同,仍算隐藏)
调用方式编译期静态绑定运行期动态绑定(多态)编译期静态绑定

三、抽象类 —— 多态的 “接口规范”

抽象类是包含纯虚函数的类,它的核心作用是 “强制派生类实现接口”,无法直接实例化对象。

3.1 纯虚函数的定义

在虚函数后加=0,就是纯虚函数:

class Car {
public:// 纯虚函数:驾驶接口virtual void Drive() = 0;
};

3.2 抽象类的特性

  1. 不能实例化对象Car car; 会编译报错;

  2. 派生类必须重写纯虚函数:否则派生类也是抽象类,无法实例化;

  3. 体现接口继承:抽象类只定义 “接口”(如Drive),不关心 “实现”,派生类负责具体实现。

3.3 抽象类代码示例

用 “汽车驾驶” 场景,抽象类规范不同车型的实现:

#include <iostream>
using namespace std;// 抽象类:Car(定义驾驶接口)
class Car {
public:virtual void Drive() = 0; // 纯虚函数:接口virtual ~Car() {} // 虚析构,避免内存泄漏
};// 派生类:Benz(实现驾驶接口)
class Benz : public Car {
public:void Drive() override {cout << "Benz: 舒适驾驶,平稳如床" << endl;}
};// 派生类:BMW(实现驾驶接口)
class BMW : public Car {
public:void Drive() override {cout << "BMW: 操控为王,贴地飞行" << endl;}
};// 派生类:Audi(实现驾驶接口)
class Audi : public Car {
public:void Drive() override {cout << "Audi: 科技感拉满,灯光秀走起" << endl;}
};// 测试函数:通过基类指针调用接口
void TestDrive(Car* car) {car->Drive(); // 多态调用
}int main() {// Car car; // 错误:抽象类不能实例化Car* benz = new Benz();Car* bmw = new BMW();Car* audi = new Audi();TestDrive(benz); // 输出:Benz: 舒适驾驶...TestDrive(bmw);  // 输出:BMW: 操控为王...TestDrive(audi); // 输出:Audi: 科技感拉满...delete benz;delete bmw;delete audi;return 0;
}

3.4 接口继承 vs 实现继承

  • 普通函数继承:是 “实现继承”—— 派生类继承基类函数的实现,直接用;

  • 虚函数继承:是 “接口继承”—— 派生类继承基类函数的接口,必须自己实现(抽象类强制)。

比如CarDrive()是接口继承,Benz必须自己写Drive()的实现;而普通函数void Func() { ... }被继承后,派生类可直接调用,无需重写。

四、多态的原理 —— 虚函数表(核心)

很多人会疑惑:为什么通过基类指针 / 引用调用虚函数,就能自动找到派生类的实现?这背后的核心是 “虚函数表”(简称 “虚表”)。

4.1 虚表的本质

含有虚函数的类,其对象会额外存储一个 “虚表指针”(_vfptr),指向一张 “虚函数表”。

  • 虚表:本质是一个存储虚函数指针的数组,数组末尾通常有一个nullptr作为结束标志;

  • 虚函数:和普通函数一样存在代码段,虚表里存的是虚函数的地址(不是函数本身);

  • 虚表指针:存在对象的内存中(通常在对象头部,x86 下占 4 字节,x64 下占 8 字节)。

4.2 基类的虚表与对象内存

先看一个基类示例,分析其内存布局:

class Base {
public:virtual void Func1() { cout << "Base::Func1()" << endl; }virtual void Func2() { cout << "Base::Func2()" << endl; }void Func3() { cout << "Base::Func3()" << endl; } // 普通函数,不进虚表
private:int _b = 1; // 成员变量,4字节(x86)
};

在 x86 环境下,sizeof(Base)8 字节—— 原因:

  • 4 字节:虚表指针(_vfptr);

  • 4 字节:成员变量_b

对象内存布局如下:

Base对象:
+------------+
| _vfptr     |  // 虚表指针,指向Base的虚表
+------------+
| _b = 1     |  // 成员变量
+------------+Base的虚表:
+------------+
| &Base::Func1 | // 虚函数1地址
+------------+
| &Base::Func2 | // 虚函数2地址
+------------+
| nullptr    |  // 结束标志
+------------+

4.3 派生类的虚表生成规则

派生类继承基类后,其虚表的生成遵循 3 步规则:

  1. 拷贝:先将基类的虚表内容完整拷贝到派生类的虚表中;

  2. 覆盖:若派生类重写了基类的某个虚函数,用派生类的虚函数地址覆盖虚表中对应基类虚函数的地址;

  3. 新增:若派生类有自己的新虚函数,按声明顺序将其地址添加到虚表末尾

4.4 派生类的虚表与对象内存

基于上面的Base类,定义派生类Derive

class Derive : public Base {
public:virtual void Func1() override { cout << "Derive::Func1()" << endl; } // 重写Func1virtual void Func4() { cout << "Derive::Func4()" << endl; } // 新虚函数
private:int _d = 2; // 派生类成员变量,4字节
};

x86 环境下,sizeof(Derive)12 字节—— 原因:

  • 4 字节:继承的虚表指针;

  • 4 字节:继承的_b

  • 4 字节:自己的_d

派生类对象内存布局与虚表:

Derive对象:
+------------+
| _vfptr     |  // 虚表指针,指向Derive的虚表
+------------+
| _b = 1     |  // 继承的成员变量
+------------+
| _d = 2     |  // 自己的成员变量
+------------+Derive的虚表:
+------------+
| &Derive::Func1 | // 覆盖:派生类Func1地址
+------------+
| &Base::Func2   | // 拷贝:基类Func2地址
+------------+
| &Derive::Func4 | // 新增:派生类Func4地址
+------------+
| nullptr    |  // 结束标志
+------------+

4.5 用代码打印虚表(验证原理)

编译器的监视窗口可能隐藏部分虚函数(如派生类新增的Func4),我们可以写一个工具函数打印虚表,直观看到虚表内容:

#include <iostream>
using namespace std;// 定义虚函数指针类型
typedef void(*VFPTR)();// 打印虚表:参数是虚表指针
void PrintVTable(VFPTR vTable[]) {cout << "虚表地址:" << vTable << endl;for (int i = 0; vTable[i] != nullptr; ++i) {printf("  第%d个虚函数:地址=0X%p,调用结果:", i, vTable[i]);VFPTR func = vTable[i];func(); // 调用虚函数,验证内容}cout << endl;
}// 基类Base
class Base {
public:virtual void Func1() { cout << "Base::Func1()" << endl; }virtual void Func2() { cout << "Base::Func2()" << endl; }void Func3() { cout << "Base::Func3()" << endl; }
private:int _b = 1;
};// 派生类Derive
class Derive : public Base {
public:virtual void Func1() override { cout << "Derive::Func1()" << endl; }virtual void Func4() { cout << "Derive::Func4()" << endl; }
private:int _d = 2;
};int main() {Base b;Derive d;// 1. 取出Base对象的虚表指针(x86下对象首4字节是虚表指针)VFPTR* baseVTable = (VFPTR*)(*(int*)&b);PrintVTable(baseVTable);// 2. 取出Derive对象的虚表指针VFPTR* deriveVTable = (VFPTR*)(*(int*)&d);PrintVTable(deriveVTable);// 打印对象大小cout << "sizeof(Base) = " << sizeof(Base) << endl;    // 8字节cout << "sizeof(Derive) = " << sizeof(Derive) << endl;// 12字节return 0;
}

运行结果

虚表地址:00F1D730第0个虚函数:地址=0X00F110B4,调用结果:Base::Func1()第1个虚函数:地址=0X00F11118,调用结果:Base::Func2()虚表地址:00F1D748第0个虚函数:地址=0X00F1117C,调用结果:Derive::Func1()第1个虚函数:地址=0X00F11118,调用结果:Base::Func2()第2个虚函数:地址=0X00F111E0,调用结果:Derive::Func4()sizeof(Base) = 8
sizeof(Derive) = 12

结果完全符合我们的分析:派生类虚表覆盖了Func1,保留了Func2,新增了Func4

4.6 多态的实现流程(终于懂了!)

回到最开始的 “买票” 示例,当执行DoBuyTicket(s)sStudent对象)时,多态的执行流程是:

  1. 传入Student对象的引用people(本质是基类引用指向派生类对象);

  2. 访问people的虚表指针_vfptr,找到Student的虚表;

  3. 在虚表中找到BuyTicket的地址(此时是Student::BuyTicket);

  4. 调用该地址对应的函数,输出 “Student: 买票 - 半价”。

而若传入Person对象,步骤 2 会找到Person的虚表,调用Person::BuyTicket—— 这就是多态的本质!

4.7 静态绑定 vs 动态绑定

  • 静态绑定(早绑定):编译期确定函数调用地址,如普通函数、重载函数;

  • 动态绑定(晚绑定):运行期通过虚表找到函数地址,如多态的虚函数调用。

比如:

Person p;p.BuyTicket(); // 静态绑定:编译期确定调用Person::BuyTicketPerson& ref = Student();ref.BuyTicket(); // 动态绑定:运行期通过虚表找到Student::BuyTicket

五、单继承与多继承的虚表差异

实际开发中,继承关系分单继承和多继承,两者的虚表结构不同,我们重点看派生类的虚表特点。

5.1 单继承的虚表(一个虚表)

单继承下,派生类只有一个虚表,虚表内容按 “拷贝→覆盖→新增” 规则生成,如 4.4 节的Derive类,这里不再赘述。

5.2 多继承的虚表(多个虚表)

多继承下,派生类会有多个虚表(每个基类对应一个虚表),未重写的虚函数会放入第一个基类的虚表中。

多继承代码示例
#include <iostream>
using namespace std;typedef void(*VFPTR)();
void PrintVTable(VFPTR vTable[]) {cout << "虚表地址:" << vTable << endl;for (int i = 0; vTable[i] != nullptr; ++i) {printf("  第%d个虚函数:0X%p → ", i, vTable[i]);VFPTR func = vTable[i];func();}cout << endl;
}// 基类1
class Base1 {
public:virtual void Func1() { cout << "Base1::Func1()" << endl; }virtual void Func2() { cout << "Base1::Func2()" << endl; }
private:int _b1 = 1;
};// 基类2
class Base2 {
public:virtual void Func1() { cout << "Base2::Func1()" << endl; }virtual void Func2() { cout << "Base2::Func2()" << endl; }
private:int _b2 = 2;
};// 派生类:多继承Base1和Base2
class Derive : public Base1, public Base2 {
public:// 重写两个基类的Func1virtual void Func1() override { cout << "Derive::Func1()" << endl; }// 新增虚函数Func3virtual void Func3() { cout << "Derive::Func3()" << endl; }
private:int _d = 3;
};int main() {Derive d;// 1. 取出Base1对应的虚表(对象首4字节)VFPTR* vTable1 = (VFPTR*)(*(int*)&d);PrintVTable(vTable1);// 2. 取出Base2对应的虚表(跳过Base1的大小:4字节虚表指针 + 4字节_b1 = 8字节)VFPTR* vTable2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));PrintVTable(vTable2);return 0;
}

运行结果

虚表地址:009ED730第0个虚函数:0X009E117C → Derive::Func1()  // 重写的Func1第1个虚函数:0X009E1118 → Base1::Func2()   // Base1的Func2(未重写)第2个虚函数:0X009E11E0 → Derive::Func3()  // 新增的Func3(放入第一个虚表)虚表地址:009ED748第0个虚函数:0X009E117C → Derive::Func1()  // 重写的Func1第1个虚函数:0X009E11A0 → Base2::Func2()   // Base2的Func2(未重写)

结论

  • 多继承派生类有多个虚表(Base1 和 Base2 各一个);

  • 重写的虚函数(Func1)在两个虚表中都被覆盖;

  • 派生类新增的虚函数(Func3)放入第一个基类(Base1)的虚表中。

六、多态的实际应用场景

学完理论,更重要的是知道 “什么时候用多态”。以下是 3 个高频场景,带代码示例。

场景 1:框架设计(插件机制)

很多框架通过多态实现 “插件扩展”,比如日志框架:核心逻辑不变,新增日志类型(文件日志、控制台日志、数据库日志)时,无需修改原有代码。

// 抽象日志接口(抽象类)
class ILogger {
public:virtual void Log(const string& msg) = 0; // 日志接口virtual ~ILogger() {}
};// 控制台日志(插件1)
class ConsoleLogger : public ILogger {
public:void Log(const string& msg) override {cout << "[Console] " << msg << endl;}
};// 文件日志(插件2)
class FileLogger : public ILogger {
public:void Log(const string& msg) override {// 实际项目中会写入文件cout << "[File] " << msg << endl;}
};// 日志管理器(核心框架,无需修改)
class LogManager {
public:// 注册日志插件void AddLogger(ILogger* logger) {_loggers.push_back(logger);}// 统一打印日志(多态调用)void LogAll(const string& msg) {for (auto logger : _loggers) {logger->Log(msg);}}
private:vector<ILogger*> _loggers;
};int main() {LogManager manager;// 注册插件manager.AddLogger(new ConsoleLogger());manager.AddLogger(new FileLogger());// 打印日志:自动调用所有插件的Log方法manager.LogAll("系统启动成功!");return 0;
}

运行结果

[Console] 系统启动成功!
[File] 系统启动成功!

好处:新增 “数据库日志” 时,只需继承ILogger实现Log,无需改LogManager—— 符合 “开闭原则”(对扩展开放,对修改关闭)。

场景 2:图形绘制(不同形状渲染)

图形库中,不同形状(圆形、矩形、三角形)的绘制逻辑不同,用多态统一调用。

// 抽象形状(抽象类)
class Shape {
public:virtual void Draw() = 0; // 绘制接口virtual ~Shape() {}
};// 圆形
class Circle : public Shape {
public:void Draw() override {cout << "绘制圆形:○" << endl;}
};// 矩形
class Rectangle : public Shape {
public:void Draw() override {cout << "绘制矩形:□" << endl;}
};// 三角形
class Triangle : public Shape {
public:void Draw() override {cout << "绘制三角形:△" << endl;}
};// 绘制管理器
void DrawAllShapes(const vector<Shape*>& shapes) {for (auto shape : shapes) {shape->Draw(); // 多态调用:不同形状自动执行对应绘制逻辑}
}int main() {vector<Shape*> shapes;shapes.push_back(new Circle());shapes.push_back(new Rectangle());shapes.push_back(new Triangle());DrawAllShapes(shapes); // 统一绘制所有形状return 0;
}

场景 3:业务逻辑(支付方式选择)

电商平台的支付模块,不同支付方式(支付宝、微信、银联)的逻辑不同,用多态解耦。

// 抽象支付接口
class IPayment {
public:virtual bool Pay(double amount) = 0; // 支付接口:返回是否成功virtual ~IPayment() {}
};// 支付宝支付
class Alipay : public IPayment {
public:bool Pay(double amount) override {cout << "支付宝支付 " << amount << " 元:";// 实际调用支付宝APIcout << "支付成功!" << endl;return true;}
};// 微信支付
class WeChatPay : public IPayment {
public:bool Pay(double amount) override {cout << "微信支付 " << amount << " 元:";// 实际调用微信APIcout << "支付成功!" << endl;return true;}
};// 订单类(依赖支付接口,不依赖具体支付方式)
class Order {
public:Order(double amount) : _amount(amount) {}// 支付:传入任意支付方式bool PayOrder(IPayment* payment) {if (_amount <= 0) {cout << "订单金额无效!" << endl;return false;}return payment->Pay(_amount); // 多态调用}
private:double _amount;
};int main() {Order order(299.9); // 299.9元的订单// 用支付宝支付order.PayOrder(new Alipay());// 用微信支付order.PayOrder(new WeChatPay());return 0;
}

七、常见问题与面试考点

多态是 C++ 面试的高频题,整理了 10 个核心问题,帮你避坑。

1. inline 函数可以是虚函数吗?

答:可以,但编译器会忽略 inline 属性。因为虚函数需要放入虚表,而 inline 函数是编译期展开,两者冲突,最终函数会按普通虚函数处理。

2. 静态成员函数可以是虚函数吗?

答:不能。静态成员函数属于,没有this指针,无法访问对象的虚表指针,因此无法放入虚表,自然不能是虚函数。

3. 构造函数可以是虚函数吗?

答:不能。对象的虚表指针是在构造函数初始化列表阶段才初始化的,若构造函数是虚函数,此时虚表指针尚未就绪,无法找到虚函数地址,逻辑矛盾。

4. 析构函数为什么建议设为虚函数?

答:避免内存泄漏!当用基类指针指向派生类对象时,若基类析构不是虚函数,delete时会静态绑定调用基类析构,派生类成员内存无法释放;设为虚函数后,会动态绑定调用派生类析构(再调用基类析构),确保内存完全释放。

5. 虚表是在什么时候生成的?存在哪里?

答:虚表在编译阶段生成,存储在代码段(常量区),不是在运行时动态生成。对象中存储的是虚表指针,不是虚表本身。

6. 一个类有多少张虚表?

答:一个类(无论有多少对象)只有一张虚表,所有对象共享这张虚表,对象中只存虚表指针。

7. 多继承中,派生类有几张虚表?

答:派生类有N 张虚表(N 是直接继承的基类个数),每个基类对应一张虚表,派生类新增的虚函数放入第一个基类的虚表中。

8. 虚函数和普通函数的调用效率哪个高?

答:分情况:

  • 普通对象调用:效率相同,都是静态绑定;

  • 基类指针 / 引用调用:普通函数效率高(静态绑定),虚函数需要运行时查虚表(动态绑定),有轻微开销。

9. 什么是抽象类?抽象类的作用是什么?

答:包含纯虚函数的类是抽象类。作用:

  1. 强制派生类实现接口(不实现则派生类也是抽象类,无法实例化);

  2. 体现接口继承,解耦接口与实现。

10. 多态的实现原理是什么?

答:核心是 “虚函数表 + 虚表指针”:

  1. 含有虚函数的类,其对象会存储一个虚表指针(_vfptr);

  2. 类的虚函数地址存储在虚表中,虚表在编译期生成;

  3. 派生类虚表按 “拷贝→覆盖→新增” 规则生成;

  4. 通过基类指针 / 引用调用虚函数时,运行期通过虚表指针找到对应虚表,再找到虚函数地址,实现动态绑定(多态)。

八、总结

多态是 C++ 面向对象的灵魂,它通过 “虚函数表” 机制,实现了 “同一接口,多种实现”,让代码更灵活、更易扩展。

核心要点回顾:

  1. 多态的两个条件:基类指针 / 引用调用虚函数 + 派生类重写虚函数;

  2. 虚函数重写的例外:协变、析构函数重写;

  3. 抽象类:含纯虚函数,强制派生类实现接口;

  4. 原理:虚表指针指向虚表,运行期动态绑定;

  5. 应用:框架设计、插件机制、业务逻辑解耦。

掌握多态,不仅能写出更优雅的代码,更能理解面向对象的设计思想(如开闭原则)。建议结合本文代码动手实践,感受多态的魅力!

http://www.dtcms.com/a/393563.html

相关文章:

  • ​​如何用 Webpack 或 Vite 给文件名(如 JS、CSS、图片等静态资源)加 Hash?这样做有什么好处?​​
  • QT-数据库编程
  • FastAPI + APScheduler + Uvicorn 多进程下避免重复加载任务的解决方案
  • 数据库造神计划第十八天---事务(1)
  • Docker在Linux中离线部署
  • 面阵vs线阵工业相机的触发方式有什么不同?
  • 【Hadoop】HBase:构建于HDFS之上的分布式列式NoSQL数据库
  • 拉取GitHub源码方式
  • 【国二】【C语言】改错题中考察switch的用法、do while执行条件的用法
  • 23种设计模式之【命令模式模式】-核心原理与 Java 实践
  • APP持续盈利:简单可行实行方案
  • qt 操作pdf文档小工具
  • Web3 开发者周刊 68 | EF 将成立一个新的 AI 团队
  • [OpenGL]相机系统
  • 软件体系结构——负载均衡
  • Unity 游戏引擎中 HDRP(高清渲染管线) 的材质着色器选择列表
  • 系统架构设计师(现代计算机系统架构和软件开发)错题集
  • 七、Linux创建自己的proc文件
  • 理解CSS中的100%和100vh
  • [特殊字符] Chrome浏览器证书导入指南
  • 15-用户登录案例
  • Kurt-Blender零基础教程:第3章:材质篇——第1节:材质基础~原理化BSDF,添加有纹理材质与用蒙版做纹理叠加
  • 南京大学 - 复杂结构数据挖掘(一)
  • 嵌入式系统、手机与电脑:一场技术演化的“三角关系”
  • Go语言常用的第三方开发包教程合集
  • 鸿蒙Next ArkTS卡片进程模型解析:安全高效的UI组件隔离之道
  • ubuntu linux 控制wifi功能 dbus控制
  • `TensorBoard`、`PyTorchViz` 和 `HiddenLayer` 深度学习中三个重要的可视化工具
  • 本地设备ipv6默认网关和路由器ipv6默认网关的区别
  • 云原生docker在线yum安装