Bootstrap

【c++】多态

【c++】多态

在面向对象程序设计中,多态性是一项核心技术,讨论的是不同层次/同一层次 之间同名成员函数关系问题

分为编译时多态运行时多态
联编:标识符名和存储地址联系在一起的过程、函数调用函数入口地址相结合的过程


一、编译时多态

定义:在编译阶段确定调用的具体函数,通过静态联编实现。
静态联编:函数地址早绑定,编译链接阶段将函数调用和函数实现关联起来。
实现方式

  1. 函数重载:同一作用域内,同名函数通过参数列表(类型、数量、顺序)区分。
  2. 运算符重载:通过operator关键字为类定义特定运算符的行为。
  3. 函数模板

示例

class Calculator {
public:
    int add(int a, int b) { return a + b; }
    double add(double a, double b) { return a + b; }
};
// 编译器根据参数类型选择正确函数
Calculator calc;
calc.add(1, 2);      // 调用int版本
calc.add(1.5, 2.5);  // 调用double版本

二、运行时多态

定义:在程序运行时根据对象的实际类型确定调用的函数,通过动态联编实现。

动态联编

  • 函数地址晚绑定,在运行时将函数调用和函数实现关联。
  • c++中 使用类类型的指针或引用调动虚函数,程序在运行时选择虚函数(查虚表)的过程称为动态联编

实现方式:公有继承+虚函数+指针或引用调动虚函数

实现原理:虚表指针(vfptr)+虚表(vtable)+动态联编

1公有继承+虚函数+指针或引用调动虚函数

相关类型共同特征转移到基类中,将共同的方法声明为共有的虚函数接口,派生类继承基类,重写虚函数,完成具体功能,调用时使用基类的指针/引用指向派生类对象,对虚函数的调用会自动绑定到派生类重写的虚函数上。

同一接口,多种实现方式

1.1虚函数
  • 虚函数:在基类中使用virtual关键字声明,派生类可重写的函数
  • 支持运行时多态

虚函数重写规则

  • 函数原型一致:派生类虚函数必须与基类虚函数的名称、参数列表和返回类型(协变除外)一致。
  • 协变返回类型:允许返回类型为基类返回类型的派生类(仅限指针/引用)。
  • override关键字(C++11):显式标记重写,增强代码可读性并防止签名错误。

关键限制与特性

  • 静态成员函数,友元函数,外部函数不能为虚函数。
  • 构造函数,拷贝构造函数:不能为虚函数(vptr在构造期间初始化)。
  • 析构函数应声明为虚函数,确保正确调用派生类析构函数。
  • 纯虚函数:通过virtual void func() = 0;定义接口,使类成为抽象基类,抽象类无法实例化对象。

其它

  • 函数执行速度稍慢,因为需要查表操作
  • 类的成员函数为虚函数,则该类派生出的所有类型中 该函数均保持虚函数特性。
2虚表指针(vfptr)+虚表(vtable)+动态联编
2.1虚表指针(vptr)
  • 每个对象实例包含一个指向其类虚表的指针(vptr),位于最顶层基类中。
  • 构造函数中隐式初始化vptr,指向当前类的虚表。

2.1.1 对象内存布局

+-------------------+
| vptr              | → 指向类的虚表
| 成员变量1          |
| 成员变量2          |
| ...               |
+-------------------+
2.2虚表(vtable)
  • 虚函数地址表,存储该类所有虚函数的地址的指针数组。
  • 类型设计有虚函数,这个类就有虚函数表
  • 虚表在编译时生成,存放在只读数据段
  • 每个类的虚表只有一份被该类所有对象共享

2.2.1 虚表结构

+-------------------+
| 虚表 (vtable)     |
+-------------------+
| RTTI 类型识别信息  |  // 运行时类型信息
| 虚函数1的地址      |
| 虚函数2的地址      |
| ...              |
| nullptr          |
+-------------------+
2.3派生类虚表生成

派生类将基类的虚表拷贝下来,并将重写的虚函数同名覆盖掉–>基类指针由于自身类型,只能调用派生类中自己定义过的同名函数。不能调用只属于基类的虚函数
派生类新增虚函数添加到复制的基类的虚表末尾。

2.4动态联编

当通过指针或引用调用虚函数时:

  1. 通过对象的vptr找到虚表。
  2. 从虚表中查找对应虚函数的地址。
  3. 执行该地址指向的函数。

示例

class Animal {
public:
    virtual void speak() { cout << "Animal sound" << endl; }
};

class Dog : public Animal {
public:
    void speak() override { cout << "Woof!" << endl; }
};

Animal* animal = new Dog();
animal->speak();  // 输出 "Woof!"(动态绑定)

后面会介绍 多态中 构造函数 析构函数 以及RTTI

;