文章目录
- 1. 认识多态
- 2. 多态的定义和实现
- 2.1 构成多态的必要条件
- 2.2 虚函数
- 2.3 虚函数的重写或覆盖
- 2.4 协变(了解)
- 2.5 析构函数的重写
- 2.6 override和final关键字
- 2.7 重载、重写、隐藏对比
- 3. 纯虚函数和抽象类
- 4. 多态原理
- 4.1 虚函数表指针
- 4.2 多态的实现
- 4.3 静态绑定和动态绑定
- 4.4 虚函数表
1. 认识多态
- 通俗点讲,多态其实就是多种形态,它分为编译时多态(静态多态)和运行时多态(动态多态)
- 编译时多态(静态多态):主要就是之前讲过的函数模版和函数重载,它们在编译阶段,通过传不同类型的参数来调用对应的函数,通过参数不同就可以达到多种形态
- 运行时多态(动态多态):具体一点就是传不同对象就可以完成不同的行为(函数),从而达到多种形态。例如:对于买票来说,学生买票半价,普通人全价,军人优先买票。
2. 多态的定义和实现
- 多态是一个继承关系下的类的对象,去调用同一个函数,产生了不同的行为。
2.1 构成多态的必要条件
- 必须是基类的指针或引用调用虚函数
- 被调用的函数必须是虚函数,并且完成了虚函数的重写/覆盖
2.2 虚函数
- 简单来说,虚函数就是在类成员函数前面+关键字virtual修饰,该成员函数也被称为虚函数,注意:非成员函数不能+virtual修饰
2.3 虚函数的重写或覆盖
- 虚函数的重写或覆盖:派生类中有一个跟基类完全相同的虚函数(即返回值、函数名、参数列表类型完全相同),称为派生类的虚函数重写了基类的虚函数
- 关于多态,可以看下面三组对比,就明白为啥只有使用基类指针/引用调用虚函数才会实现多态的效果了
- 在重写虚函数时,派生类的虚函数可以不+virtual,也可以和基类的虚函数构成重写/覆盖(基类被继承后,其虚函数的属性也被派生类继承了下来)。但是实际项目中,最好还是加上
- 接下来我们看一道关于多态的选择题
2.4 协变(了解)
- 派生类重写虚函数时,与基类虚函数返回值类型不同,即基类虚函数返回基类对象的指针/引用,派生类虚函数返回派生类对象的指针/引用时,称为协变。实际项目中意义不大,了解即可
2.5 析构函数的重写
- 基类的析构函数为虚函数,那么派生类的析构函数无论加不加virtual修饰,都和基类的析构函数构成重写(主要是因为编译器将类的析构函数名字都特殊处理为destructor)
2.6 override和final关键字
- 可以看出,C++对于虚函数重写要求比较高,但也有可能有些情况下就疏忽忘写,例如函数名写错,写错参数等,因此C++就提出关键字override来检查是否达到虚函数重写
- 如果不想让派生类去重写虚函数,可用final修饰
2.7 重载、重写、隐藏对比
3. 纯虚函数和抽象类
- 在虚函数的后面写上=0,则这个函数为纯虚函数,纯虚函数不需要定义实现(实现也没啥意义,因为要被派生类重写,但是语法上可以实现),只要声明即可
- 包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象
- 如果派生类继承后不重写纯虚函数,那么派生类也是抽象类
- 纯虚函数某种程度上强制了派生类重写虚函数,因为不重写实例化不出对象
4. 多态原理
4.1 虚函数表指针
- 可以看出,对于含有虚函数的类,其内都有一个虚函数表指针vfptr(全称virtual function ptr),该指针指向虚函数的地址(虚函数表指针可能放在对象前面,也可能放在对象后面,具体看平台)
4.2 多态的实现
- 由此可以看出,满足多态条件后,底层不再是编译时通过调用对象确定函数的地址,而是运行时到指向的对象的虚表中确定对应的虚函数的地址,这样就实现了指针或引用指向基类就调用基类的虚函数,指向派生类就调用派生类对应的虚函数
- 第一张图,ptr指向的Person对象,调用的是Person的虚函数;第二张图,ptr指向的Student对
象,调用的是Student的虚函数
- 多态也可以发生在多个派生类
4.3 静态绑定和动态绑定
- 对不满足多态条件的函数,调用时是在编译阶段绑定的,也就是编译时确定调用该类函数的地址,叫做静态绑定
- 满足多态条件的函数,在运行时,到指向对象的虚函数表中找到调用该类函数的地址,也叫做动态绑定
4.4 虚函数表
- 基类对象的虚函数表中存放基类所有虚函数的地址。同类型的对象共用同一张虚表,不同类型的对象各自有独立的虚表,所以基类和派生类有各自独立的虚表
- 派生类由两部分构成,继承下来的基类和自己的成员,一般情况下,继承下来的基类中有虚函数表指针,自己就不会再生成虚函数表指针。但是要注意的这里继承下来的基类部分虚函数表指针和基类对象的虚函数表指针不是同一个,就像基类对象的成员和派生类对象中的基类对象成员也独立的
- 派生类中重写的基类的虚函数,派生类的虚函数表中对应的虚函数就会被覆盖成派生类重写的虚函数地址
- 派生类的虚函数表中包含,(1)基类的虚函数地址,(2)派生类重写的虚函数地址完成覆盖,派⽣类自己的虚函数地址三个部分
- 虚函数表本质是⼀个存虚函数指针的指针数组,⼀般情况这个数组最后面放了⼀个0x00000000标记。(这个C++并没有进行规定,各个编译器自行定义的,vs系列编译器会再后面放个0x00000000标记,g++系列编译不会放)
- 虚函数存在哪的?虚函数和普通函数⼀样的,编译好后是⼀段指令,都是存在代码段的,只是虚函数的地址又存到了虚表中
- 虚函数表存在哪的?这个问题严格说并没有标准答案C++标准并没有规定,我们写下面的代码可以对比验证⼀下。vs下是存在代码段(常量区)