【c++】多态
在面向对象程序设计中,多态性是一项核心技术,讨论的是不同层次/同一层次 之间同名成员函数关系问题
分为编译时多态和运行时多态
联编:标识符名和存储地址联系在一起的过程、函数调用和函数入口地址相结合的过程
一、编译时多态
定义:在编译阶段确定调用的具体函数,通过静态联编实现。
静态联编:函数地址早绑定,编译链接阶段将函数调用和函数实现关联起来。
实现方式:
- 函数重载:同一作用域内,同名函数通过参数列表(类型、数量、顺序)区分。
- 运算符重载:通过
operator
关键字为类定义特定运算符的行为。 - 函数模板
示例:
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动态联编
当通过指针或引用调用虚函数时:
- 通过对象的vptr找到虚表。
- 从虚表中查找对应虚函数的地址。
- 执行该地址指向的函数。
示例:
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