上一章节:
七、重学C++—静态多态(编译期)-CSDN博客https://blog.csdn.net/weixin_36323170/article/details/146999362?spm=1001.2014.3001.5502
本章节代码:
cpp/dynamicPolymorphic.cpp · CuiQingCheng/cppstudy - 码云 - 开源中国
https://gitee.com/cuiqingcheng/cppstudy/blob/master/cpp/dynamicPolymorphic.cpp

目录
上一章节:
本章节代码:
一、引言
二、概念:什么是运行时多态?
三、实现机制:虚函数
虚函数表和虚函数表指针的作用
四、纯虚函数,抽象类/接口类
抽象类
接口类
五、多态的优点和应用场景
优点
应用场景
六、总结
一、引言
在 C++ 的奇妙世界里,多态是一个强大而迷人的特性,它就像代码世界中的变形金刚,让程序能够根据不同的情况展现出多样的行为。而
运行期多态,作为多态的重要组成部分
,更是为程序带来了无与伦比的灵活性和扩展性。今天,我们就一起来深入探秘 C++ 运行期多态的奥秘。
二、概念:什么是运行时多态?
运行期多态,也被称为
动态多态
,是指在
程序运行时才确定具体要调用哪个函数
。它通过
虚函数和继承机制来实现,允许我们使用基类的指针或引用调用派生类的函数,从而实现不同对象的不同行为
。简单来说,就是在运行时
根据实际对象的类型来决定执行哪个函数
,就像变形金刚在战斗中根据不同的场景变换形态一样。
三、实现机制:虚函数
虚函数是实现运行期多态的关键。在基类中,我们使用 “
virtual”
关键字来声明虚函数,
派生类可以重写这些虚函数以实现自己的特定行为
。当通过基类的指针或引用调用虚函数时,程序会在运行时根据指针或引用实际指向的对象类型来决定调用哪个版本的函数。
class base{
public:
virtual void func() // 虚函数
{ }
}
class A:public base{
public:
void func() override
{
// 重写实现虚函数
}
}
每一个
包含虚函数的类中均有一个虚函数表指针,指向虚函数表;如下图:

虚函数表和虚函数表指针的作用
虚函数表(
VTable):
每个
包含虚函数的类都有一个虚函数表,这是一个存储虚函数地址的数组
。表中的每个条目都是一个指向虚函数的指针,这些虚函数按照它们在类中声明的顺序排列。
虚函数表指针(
VPTR):
每个包含虚函数的类的对象都有一个虚函数表指针,它指向该类对应的虚函数表
。借助这个指针,对象能够在运行时找到正确的虚函数来调用。
实例:一个动物的基类,均有叫这个实现函数,对于两个继承自动物类的狗类/猫类,分别实现其叫这个函数,代码如下:
/***
* C++ 多态
* 动态多态
* 虚函数/纯虚函数
* 抽象类/接口类
*/
#include <iostream>
// 基类 Animal
class Animal {
public:
// 虚函数 speak()
virtual void speak() {
std::cout << "动物发出声音" << std::endl;
}
};
// 派生类 Cat
class Cat : public Animal {
public:
// 重写 speak() 函数
void speak() override {
std::cout << "喵~" << std::endl;
}
};
// 派生类 Dog
class Dog : public Animal {
public:
// 重写 speak() 函数
void speak() override {
std::cout << "汪!" << std::endl;
}
};
int main() {
// 创建 Cat 和 Dog 对象
Cat cat;
Dog dog;
// 使用基类指针指向派生类对象
Animal* animal1 = &cat;
Animal* animal2 = &dog;
// 调用虚函数
animal1->speak(); // 输出:喵~
animal2->speak(); // 输出:汪!
animal1->Animal::speak(); // 输出:动物发出声音
return 0;
}
这里
基类的指针,会根据实际指向的对象类型,从而决定默认调用的是重写的虚函数
,若还想调用基类原来的虚函数,需要指定作为基类方可调用;
四、纯虚函数,抽象类/接口类
上面定义的基类中,虚函数还是实现了,若
一个虚函数没有实现的代码段体,则是纯虚函数,定义如下:
virtual int func() = 0;
抽象类
包含纯虚函数的类被称为抽象类
,
抽象类不能被实例化
,只能作为基类被派生类继承。
派生类必须重写抽象类中的纯虚函数,否则派生类也会成为抽象类。只有实现了纯虚函数的派生类,才能实例化对象。
接口类
特殊的抽象类,
类中所有函数都是纯虚函数
实例如下:
/***
* C++ 多态
* 动态多态
* 虚函数/纯虚函数
* 抽象类/接口类
*/
#include <iostream>
// 抽象类
class Organism{
public:
virtual void growup() = 0;
virtual void eating(){
std::cout << "eating everything" << std::endl;
};
};
// 基类 Animal 还是抽象类,无法实例化对象
class Animal : public Organism{
public:
// 虚函数 speak()
virtual void speak() {
std::cout << "动物发出声音" << std::endl;
}
};
// 派生类 Cat
class Cat : public Animal {
public:
void growup(){
std::cout<< "猫越长越大" << std::endl;
}
// 重写 speak() 函数
void speak() override {
std::cout << "喵~" << std::endl;
}
};
// 派生类 Dog
class Dog : public Animal {
public:
void growup(){
std::cout<< "狗越长越大" << std::endl;
}
// 重写 speak() 函数
void speak() override {
std::cout << "汪!" << std::endl;
}
};
int main() {
// 创建 Cat 和 Dog 对象
Cat cat;
Dog dog;
// 使用基类指针指向派生类对象
Animal* animal1 = &cat;
Animal* animal2 = &dog;
// 调用虚函数
animal1->speak(); // 输出:喵~
animal2->speak(); // 输出:汪!
animal1->growup(); // 输出:喵~
animal2->growup(); // 输出:汪!
animal1->Animal::speak(); // 输出:动物发出声音
return 0;
}
五、多态的优点和应用场景
优点
- 灵活性和扩展性:运行期多态允许我们在不修改现有代码的情况下,轻松地添加新的派生类和功能。
- 代码复用:通过使用基类的指针或引用,我们可以编写通用的代码来处理不同类型的对象,从而提高代码的复用性。例如,图形绘制程序中,我们可以编写一个通用的函数来计算不同图形的面积,而不需要为每种图形编写单独的函数。
- 可维护性:运行期多态使得代码的结构更加清晰,易于理解和维护。通过将不同的行为封装在不同的派生类中,我们可以避免代码的冗余和混乱。
应用场景
- 游戏开发:在游戏开发中,运行期多态可以用于实现不同角色的不同行为,如攻击、防御、移动等。例如,不同类型的角色(如战士、法师、刺客)可以继承自一个基类 Character,并分别重写 attack()、defend()、move() 等函数,以实现不同的行为。
- 图形处理:在图形处理中,运行期多态可以用于实现不同图形的不同绘制方法。例如,不同类型的图形(如圆形、矩形、三角形)可以继承自一个基类 Shape,并分别重写 draw() 函数,以实现不同的绘制方法。
- 数据库访问:在数据库访问中,运行期多态可以用于实现不同数据库的不同访问方法。例如,不同类型的数据库(如 MySQL、Oracle、SQLite)可以继承自一个基类 Database,并分别重写 connect()、query()、insert() 等函数,以实现不同的访问方法。
六、总结
运行期多态是 C++ 中一个非常强大和重要的特性,它通过虚函数和继承机制实现了在程序运行时根据实际对象的类型来决定执行哪个函数的功能。