C++多态

一、什么是多态

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会
产生出不同的状态。
切片时会导致指针引用指向的类型和自身类型不同,
多态看的是ptr或ref的对象的类型,而不是ptr和ref自身的类型,然后去调相应的虚函数。

二、多态的实现条件

继承体系+虚函数重写+基类调用

C++多态_第1张图片

 虚函数是在前面virtual的成员函数

虚函数的重写 ( 覆盖 ) 派生类中有一个跟基类完全相同的虚函数 ( 即派生类虚函数与基类虚函数的
返回值类型、函数名字、参数列表完全相同 ) ,称子类的虚函数重写了基类的虚函数。
参数列表只看类型、顺序,不看缺省值。
这是因为重写时,子类会将父类的 返回值类型、函数名字、参数列表 直接替换下来,这也是子类中虚函数前可以省略virtual的原因。
C++多态_第2张图片

这里无论是引用p,还是指针ptr,都是看其指向对象的类型来调用函数,即形成了多态。 

重写的2个例外:

1、协变

C++多态_第3张图片

 这里父子类可以不是虚函数所在的类,可以是任意的父子类。

2、析构函数

C++多态_第4张图片 

C++多态_第5张图片

由于向上转换的存在,C++允许一个父类指针指向子类对象。

此时,按照以前的理解,delete p,根据p的类型去调用基类的析构函数,就会导致派生类没有被清理,进而导致内存泄漏等问题。

这里我们期望实现多态调用析构函数:即根据p指向的类型是派生类,然后去调用派生类的析构。

但是不同类的析构函数的函数名不同,该怎么办呢?

这里编译器将所有的析构函数在底层都替换为destructor,这样满足三同,然后再分别重写各自的析构函数,就实现多态调用析构函数了。

总结:为了实现多态调用析构函数,必须将其变为虚函数。

重载隐藏重写的区别

C++多态_第6张图片

C++11关键字

1、override  

C++多态_第7张图片

 在基类虚函数中加override,会自动检查子类虚函数是否重写。

2、final

C++多态_第8张图片

 基类虚函数加final则不能被重写。

C++多态_第9张图片

 如果想要实现类不能被继承,可以将其构造函数私有,就无法在其派生类中自动调用了。

当要调用A类对象的构造时,可以使用静态函数返回一个构造好的A类对象。

C++多态_第10张图片

或者直接在类后面加final。 

三、虚函数表

C++创建对象模型时,如果类中有虚函数,会多一个_vfptr的函数指针,指向虚函数表,表中是类所有虚函数的地址。

一个类的所有对象共用一张虚表。

派生类对象中也有一个虚表指针,对象由两部分构成,一部分是父类继承下来的成员,虚
表指针也就是存在部分的另一部分是自己的成员。

派生类的虚表生成

1.先将基类中的虚表内容拷贝一份到派生类虚表中 
2.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 
3.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后

存储位置

C++多态_第11张图片

 虚表存在常量区/代码段。

用基类指针/引用实现多态的原因 

C++多态_第12张图片

打印虚表

C++多态_第13张图片

 ps中,有3个函数。

st中,重写了BuyTicket所以地址改变,Func1和Func2没有重写,仍为拷贝过来的原地址

自己的Func3跟在自己的虚表后面。

动态/静态多态

C++多态_第14张图片

编译时只检查语法,指针不知道调用的是哪个函数,要在运行的时候去虚函数表里面找然后调用

重载在编译时根据函数名修饰规则就确定了,直接根据函数地址调用即可。 

虚函数调用原理:

C++多态_第15张图片

 四、多继承下的虚表

C++多态_第16张图片

C++多态_第17张图片 C++多态_第18张图片

多继承体系下,Derive继承了两个类,分别将它们的虚表也继承下来 

func2没有重写,因此与原来的地址相同。

func1都重写了,为什么两个地址不一样?

C++多态_第19张图片

这是由于C++对象模型导致的。ptr2指向的是Derive对象中的base2对象,指针或引用要指向这个Derive对象,而不是它其中的base2对象,因此要先调整到前4个字节,然后传this指针指向虚表指针,然后再调用。

这里不同编译器的处理结果也不同,也可能是相同的地址。VS2022下是不同的。

 

你可能感兴趣的:(C++人生,c++,开发语言)