目录

前言

一.多态的概念

        1.1 概念

        1.2 多态的构成条件

        1.3 虚函数

        1.4 虚函数的重写

        1.4.1 协变

        1.4.2 析构函数的重写

        1.4.3 C++11里的override和final关键字

         1.5 抽象类

         1.6 接口继承和实现继承

 二.多态的原理

 2.1 虚函数表

2.2 派生类中的虚表指针

2.3 虚表保存在哪

 2.4 多态原理

 2.5 动态绑定和静态绑定

三.单继承和多继承的虚函数表

3.1 单继承中的虚表

3.2 多继承中的虚表


前言

        我认为多态是因为有了虚函数,通过继承,派生类可以重写虚函数,才有了C++的多态。所以要了解多态的原理,需要重点了解虚函数和虚基表。

一.多态的概念

        1.1 概念

        多态就是当要完成某个行为,当不同的对象去完成时会产生不同的效果。或者是说,不同的对象处理某一件事有不同的方法。

        比如:在火车站买票,普通成年人,需要全价买票,嘘声可以半价买票,军人可以优先买票。

        1.2 多态的构成条件

        多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。

        条件:

  • 被调用的函数必须是虚函数,并且派生类必须对基类的虚函数进行重写。
  • 必须通过基类的指针或者引用调用虚函数

第一点注意点是:调用的函数只能是虚函数,并且派生类必须对基类的选和拿书进行重写。如果不重写,只是继承,实现方法一样。如果基类的不是虚函数,重写函数只是构成隐藏。

第二个注意点就是:必须通过基类的指针或者引用调用虚函数。

满足多态:跟对象的类型无关,跟指向的对象有关,指向哪个对象调用就是它的虚函数。

不满住多态:跟调用对象的类型有关,类型是什么就调用谁的虚函数。

如果不满足多态条件:

1.不重写虚函数

2.不是虚函数,重写

3.不用指针或者引用调用

        1.3 虚函数

        虚函数:被virtual修饰的成员函数称为虚函数。注意:修饰的是成员函数。

class Person
{
public:
	//虚函数
	virtual void BuyTicket()
	{
		cout 

         1.4 虚函数的重写

        虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类的虚函数的返回值类型,函数名字,参数列表完全相同),但是函数的实现不同,称派生类重写了基类的虚函数。

class Person
{
public:
	//虚函数
	virtual void BuyTicket()
	{
		cout 

 注意:

        虚函数的重写,需要函数名,参数,返回值类型一样。但是派生类只是继承了函数的接口,接口就是函数名,参数,返回值类型。派生类重写只是实现不同。

        1.4.1 协变

        派生类重写基类虚函数,与基类虚函数的返回值不同。并且基类虚函数的返回值基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用,称为协变。

 也可以是返回别的类的指针或者引用

class A{};
class B : public A {};

class Person {
public:
    //返回A的
    virtual A* f() {return new A;}
};
class Student : public Person {
public:
    //返回B的
    virtual B* f() {return new B;}
};

         1.4.2 析构函数的重写

问题:

 所以析构函数需要定义成虚函数,来构成多态:

这里是一个注意点,申请空间为派生类,但是赋值给基类时,需要将析构函数写成虚函数,构成多态。

         1.4.3 C++11里的override和final关键字

        1. fanal:修饰虚函数,表示该虚函数不能被继承,不能进行重写。

 修饰类,类不能被继承

 在C++98中,为了不让类被继承,可以将基类的构造函数私有化(private),于是派生类就不能构造属于基类的成员。

        2.override 检查派生类虚函数是否重写了基类的虚函数,如果没有编译错误。

这个只能修饰派生类的虚函数,不能修饰基类虚函数

 override关键字最好用来检查派生类虚函数接口(函数名,参数,返回值)是否写错

 1.5 抽象类

在虚函数后面写上=0,这个函数称为纯虚函数。

包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承之后也不能实例化出对象,只有重写的纯虚函数,派生类才能实例化出对象。

只有派生类经过纯虚函数重写才能实例化出对象。但是基类还是抽象类,不能实例化对象。

纯虚函数的作用:

        1.一定程度上强制了派生类对纯虚函数的重写,如果不重写,派生类就不能实例化对象。

        2.表示抽象的类型

应用场景:

        比如:比如,只说一辆车,车是抽象的,是一个很笼统的概念。因为有多车,不知道具体什么车,并且你不会拿车实例化对象,车这个类就可以定义成抽象类,里可以写成员函数,但是不写具体实现。

        但是某个品牌的车,比如奔驰,可以继承车这个抽象类,只需要重写纯虚函数,就可以实例化对象。

        具体的继承抽象的。

1.6 接口继承和实现继承

        普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。只是可以有隐藏。

        虚函数的继承是一种接口继承,派生类只是继承了基类虚函数的接口,目的是为了重写,达成多态。

        所以如果不实现多态,不要定义成虚函数。

 二.多态的原理

        2.1 虚函数表

 通过观察我们发现,对象b是8个字节,除了_num外,还有_vfptr指针。这个指针是我们叫做虚函数表指针,简称虚表指针。一个含有虚函数的类中至少有一个这样的指针。

_vfptr指针变量保存的是虚函数表的起始地址。

虚函数表实际是一个函数指针数组,虚函数表简称虚表。虚表里面保存的都是虚函数的地址。        

2.2 派生类中的虚表指针

 派生类不重写基类的虚函数。

 通过上面现象说明一个结论:

        派生类会继承基类的虚函数,会继承基类的虚表。但是派生类和基类的_vfptr变量内容不相等,说明两个虚表不是同一种虚表,只是虚表里的内容相同,所以会调用同一个函数。

派生类重写虚函数

 通过上面现象说明一个结论:

        派生类重写基类虚函数,会重写派生类虚函数表里的内容,将对应位置覆盖层重写虚函数的指针。

派生类增加虚函数

 注意:

        1.类中有虚函数只是这个类中多以虚函数表指针,不是将虚函数表保存到类中。

        2.虚函数表最后会以nullptr结尾。

        3.同类型的对象共用一张虚表,可以理解成一个类的虚表属于这个类的,实例化的对象,都公用这一张虚表。

 总结派生类虚表的生成:

1.派生类会继承基类的虚表,当两个虚表表不是一张虚表。派生类先将基表虚表的内容拷贝一份到派生类的虚表中

2.如果虚表重写虚函数,用派生类重写虚函数的地址覆盖掉虚表中对应虚函数的地址。

3.派生类增加虚函数,会在派生类虚表中声明次序增加到虚表的最后。

2.3 虚表保存在哪

        首先说明,一个具有虚函数的类_vfptr保存在前面还是后面是有平台决定的,根据上面的现象,我们平台_vfptr是保存在最前面的。

 2.4 多态原理

        多态是基于虚函数的虚函数表。构成多态,跟对象有关。如果是基类对象,会去基类的虚表中找要调用虚函数的地址,去执行虚函数的代码。如果是派生类对象,会去派生类类的虚表中找要调用虚函数的地址,去执行虚函数的代码。

        派生类虚函数重写之后,可以实现不同的对象,有不同的实现方法,展现不同的效果。

 再来段代码理解一下:

通过汇编分析,看出满足多态以后函数调用,不是再编译时确定的,是在运行起来后到对象的需表中找的。

不满足多态,是在编译时确定好的。

         2.5 动态绑定和静态绑定

  • 静态绑定又称为前期绑定,在程序编译期间确定了程序的行为,也称静态多态。比如函数重载。在编译的时候确定了调用的函数。
  • 动态绑定也称后期绑定,是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称动态多态。就像上面的,运行时在到虚表中找调用函数的地址。
  • 多态,多数都是动态绑定。

三.单继承和多继承的虚函数表

3.1 单继承中的虚表

 结论是否如上图所述:我们来打印一下虚表

打印代码

class Person 
{
public:
	virtual void func1(){ cout 

 结果和我们想的一样:

 3.2 多继承中的虚表

 根据上面的结论可以得到这样一张图:

#include

using namespace std;

class Base1
{
public:
	virtual void func1(){
		cout 

 画图表示为:

 

原文链接:https://blog.csdn.net/weixin_57023347/article/details/119978550原文链接:

最后修改日期:2021年9月7日

留言

撰写回覆或留言