编程语言
首页 > 编程语言> > C++多态原理与内部结构

C++多态原理与内部结构

作者:互联网

多态类型

多态可以分为静态多态动态多态

  1. 静态多态其实就是重载,因为静态多态是指在编译时期在形成符号表的时候,对函数名做了区分,根据参数列表来决定调用哪个函数,也叫编译时多态
  2. 动态多态是指通过子类重写父类的虚函数来实现的,因为是在运行期间决定调用的函数,所以称为动态多态,一般情况下我们不区分这两个时所说的多态就是指动态多态。

动态多态的实现与虚函数表,虚函数指针相关,下面详述。

虚函数相关

首先我们来说一下,C++中多态的表象,在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数,如果是基类,就调用基类的函数。
实际上,当一个类中包含虚函数时,编译器会为该类生成一个虚函数表,保存该类中虚函数的地址,同样,派生类继承基类,派生类中自然一定有虚函数,所以编译器也会为派生类生成自己的虚函数表。当我们定义一个派生类对象时,编译器检测该类型有虚函数,所以为这个派生类对象生成一个虚函数指针,指向该类型的虚函数表,这个虚函数指针的初始化在构造函数中完成的。后续如果有一个基类类型的指针,指向派生类,那么当调用虚函数时,从对象的前4个字节中取虚表地址,根据所指真正对象的虚函数表指针去寻找虚函数的地址,也就可以调用派生类的虚函数表中的虚函数。以此实现多态。
在这里插入图片描述
在这里插入图片描述
补充:
如果基类中没有定义成virtual,那么进行Base B; Derived D; Base *p = D; p->function();这种情况下调用的则是Base中的function()。
因为基类和派生类中都没有虚函数的定义,那么编译器就会认为不用留给动态多态的机会,就事先进行函数地址的绑定(早绑定)
详述过程就是,定义了一个派生类对象,首先要构造基类的空间,然后构造派生类的自身内容,形成一个派生类对象,那么在进行类型转换时,直接截取基类的部分的内存,编译器认为类型就是基类,那么(函数符号表[不同于虚函数表的另一个表]中)绑定的函数地址也就是基类中函数的地址,所以执行的是基类的函数。
(1)派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同
(2)基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性
(3)只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数
(4)如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加
(5)构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容易混淆(?)
(6)不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会出现未定义的行为
(7)最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)
(8)虚表是所有类对象实例共用的

编译器建立虚函数表的过程

  1. 拷贝基类的虚函数表,如果是多继承,就拷贝每个有虚函数基类的虚函数表,当然还有一个基类的虚函数表和派生类自身的虚函数表共用了一个虚函数表,也称为某个基类为派生类的主基类;
  2. 查看派生类中是否有重写基类中的虚函数,如果有,就替换成已经重写的虚函数地址;
  3. 查看派生类是否有自身的虚函数,如果有,就追加自身的虚函数到自身的虚函数表中。
    在这里插入图片描述
    编译器使用虚函数表的过程要注意的是:
    Derived *pd = new D(); B *pb = pd; C *pc = pd;
    `其中pb,pd,pc的指针位置是不同,要注意的是派生类的自身的内容要追加在主基类的内存块后。
    在这里插入图片描述

多继承与重复继承与虚继承的内部结构

多继承

在这里插入图片描述

重复继承

在这里插入图片描述

虚继承(菱形继承)

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

标签:函数,编译器,基类,多态,C++,内部结构,派生类,定义
来源: https://blog.csdn.net/RaKiRaKiRa/article/details/100830809