0%

C++多态的基本原理及使用

多态的概念、定义以及实现

多种形态,当不同的对象去执行同一种行为时,产生的不同表现形态

构成条件:在不同的继承关系的类对象,去调用同一函数,产生了不同的行为

  1. 继承关系
  2. 必须通过基类的指针或者引用调用的虚函数,一般都是用父类指针/引用指向父类以及子类实体,即都为切片行为
  3. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

必须同时满足以上条件,缺一不可

虚函数的重写:重写函数逻辑

构成虚函数重写条件:子类含有和父类接口完全相同的函数(返回值,函数名,参数完全相同),例外:协变,析构函数重写,如果不满足所有条件,但是函数名相同,则构成函数隐藏

协变:返回值类型可以不同,但是返回值类型必须是父子关系

注意:如果父类函数加了virtual声明,则子类接口完全一致的函数即使不加virtual也具有虚函数的属性,但是反过来不成立,建议一般对于所有的虚函数都加上virtual

非多态:看类型

多态:看实际指向的实体

析构函数重写:

  1. 只要父类的析构函数是虚函数,则子类的析构函数和父类的析构函数构成重写

    原因:编译器对继承关系下的所有类的析构函数的名字做了统一处理,保证了继承关系下所有的析构函数同名,一般把析构函数的名称统一处理成destructor

finaloverride关键字:

  1. final:修饰虚函数,表示该虚函数不能再被继承,其定义的函数不能被重写,体现实现继承
  2. override:检查派生类虚函数是否重写了某个虚函数,如果没有重写则编译报错,强制子类重写父类的某一个虚函数

抽象类

抽象类:包含纯函数的类,抽象类不能实例化对象

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
class A
{
public:
//纯虚函数:没有函数体的虚函数
virtual void fun() = 0;
};

class B : public A
{
public:
virtual void fun()
{
cout << "B::fun()" << endl;
}
};

void test()
{
//无法执行以下代码
//A a;
//a.fun();

B b;
A* pb = &b;
b.fun();
pb->fun();
}

抽象类存在意义,实现多态,其体现出接口继承

多态原理

虚函数表

1
2
3
4
5
6
7
8
class D
{};
class Car
{
public:
virtual void Drive()
//包含隐藏成员变量,虚函数表指针
};

包含虚函数的类:类中包含一个隐含的成员变量,即虚表指针

虚表指针:指向虚表的首地址,类型为二级指针,函数指针的指针

虚表:存放虚函数指针的数组,类型为指针数组

  1. 只存放虚函数的指针
  2. 普通函数不会存入虚表
  3. 如果子类重写了父类的虚函数,则子类虚表中对应位置使用子类虚函数指针覆盖
  4. 如果子类没有重写父类的任何虚函数,则子类完全继承父类虚表,不做任何修改
  5. 虚函数指针在虚表中的存放顺序和其声明/定义的顺序一致
  6. 子类新定义的虚函数,其虚函数指针按照声明/定义的顺序依次加入虚表的末尾
  7. 虚表一般以nullptr结束

只要类中包含虚函数,就会有虚表指针和虚表

  • 虚表指针存在对象当中
  • 虚表存放在代码段
  • 虚函数存放在代码段

多态:看实际指向的实体

多态原理:如果访问的为虚函数,则通过指针/引用找到实际指向的实体,获取实体中的虚表指针,通过虚表指针访问虚表,在虚表中找到需要执行的虚函数指针,通过虚函数指针执行具体函数行为

继承下的虚表

单继承虚表

  1. 子类继承父类虚表
  2. 用重写的虚函数指针覆盖子类对应的虚函数指针
  3. 子类新定义的虚函数,其指针按照声明/定义顺序存入虚表的末尾

多继承虚表:

  1. 虚表个数:等同于直接父类的个数
  2. 子类继承父类所有的虚表
  3. 用重写的虚函数指针覆盖对应父类的虚函数指针
  4. 子类新定义的虚函数,其函数指针按照声明/定义的顺序存入第一个直接父类虚表的末尾
-------------本文结束感谢您的阅读-------------