多态:
“多态(polymorphism)”指的是同一名字的事物可以完成不同的功能。多态可以分为编译时的多态和运行时的多态。
编译时的多态(静态多态) 主要是指函数的重载(包括运算符的重载)、模版,对重载函数的调用,在编译时就能根据实参确定应该调用哪个函数
- 函数重载(Function Overloading):同一作用域内,函数名相同但参数列表不同(参数类型、个数或顺序不同)。
- 运算符重载(Operator Overloading):如 +、<< 等运算符的重载,本质上也是函数重载。
- 模板(Templates):函数模板和类模板在编译时实例化不同的版本,也属于静态多态。
运行时的多态(动态多态) 则和继承、虚函数、函数重写、动态绑定等概念有关。
- 继承:派生类继承基类的属于和方法。
- 虚函数:基类用virtual声明虚函数,派生类通过override重写虚函数(override可以省略不写,但是子类函数的 名称、参数列表、返回类型 与父类完全一致(或返回类型是协变的,如派生类指针)。
- 动态绑定:通过基类指针或引用调用虚函数时,实际执行派生类的实现。
虚函数:
C++动态多态性是通过虚函数来实现的,虚函数允许子类(派生类)重新定义父类(基类)成员函数,而子类(派生类)重新定义父类(基类)虚函数的做法称为覆盖(override),或者称为重写。
虚函数(Virtual Function) : 使用虚函数非常简单,只需要在函数声明前面增加 virtual关键字,作用是实现函数地址晚绑定。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
using namespace std;
class Animal
{
public:
//speak函数就是虚函数 前面加关键字virtual
virtual void speak()
{
cout<<"动物在说话"<<endl;
}
};
class Cat:public Animal
{
public:
//重写 函数返回值类型 函数名 参数列表 完全相同 virtual可写可不写
void speak()
{
cout<<"小猫在说话"<<endl;
}
};
class Dog:public Animal
{
public:
void speak()
{
cout<<"小狗在说话"<<endl;
}
};
//地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话 那么这个函数地址就不能提前绑定 需要在运行阶段进行绑定 地址晚绑定 采用虚函数
void doSpeak(Animal &animal)//Animal &animal=Cat;
//如果不采用多态 地址会早绑定 不论传入是父类或者是子类对象 都会执行父类的数据 动物在说话
//如果不采用虚函数 传入子对象 一样会执行父类数据 比如这个如果不采用 会打印出 动物在说话
{
animal.speak();//这样打印出来的是子类的数据
}
void test01()
{
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
int main()
{
test01();
system("pause");
return 0;
}简单总结
1) virtual 修饰的函数将变为虚函数
2) 多态的实现需要使用父类的指针开辟子类的空间
3) 虚函数创建后会占用类的四字节空间(无论个数)
4)动态多态满足条件:
- 有继承关系
- 子类重写父类的虚函数
5)动态多态使用:
- 父类的指针或者引用 指向子类对象
**重写:**函数返回值类型 函数名 参数列表 完全相同 virtual可写可不写
多态的原理:

纯虚函数和抽象类:
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数
语法格式:
- virtual 返回值类型 函数名 (参数列表)= 0;
- 纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上=0,表明此函数为纯虚函数。(最后的=0 并不表示函数返回值为 0,它只起形式上的作用,告诉编译系统“这是纯虚函数”。)
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
1 |
|
虚析构和纯虚析构:
- 在使用多态时,如果派生类在其构造函数中分配了堆内存(或持有其他需要释放的资源,比如实例化对象),而基类未声明虚析构函数,那么当通过基类指针删除派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数,从而导致派生类中分配的资源无法被正确释放,造成内存泄漏(或资源泄漏)。
- 解决方式:将父类中的析构函数改为虚析构或者纯虚析构
- 虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
- 虚析构和纯虚析构区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
- 虚析构语法:virtual ~类名(){}
- 纯虚析构语法:
- virtual ~类名()=0;
- 类名::~类名(){}
- 纯虚析构函数和虚析构函数只需要写一个就可以了
1 |
|
总结:
- 虚析构和纯虚析构就是用来解决通过父类指针释放子类对象
- 如果子类中没有堆区数据,可以不写虚析构或纯虚析构
- 拥有纯虚析构函数的类也属于抽象类