1、虚函数
虚函数引入后,类会发生什么样的变化?
一个对象,只要他占用内存空间,那么这个对象的sizeof值至少是1。
类A的普通成员函数,它并不占用类对象的内存空间。
加入虚函数后,类A对象的sizeof值就变成了4。
当一个或多个虚函数加入到一个类中之后,编译器会向类中插入一个看不见的成员变量,在类中,这个看不见的成员变量类似是下面这样的伪代码。这个看不见的成员变量有一个名字就叫虚函数表指针(简称vptr),虚函数表指针正好是4个字节,这4个字节是占用类对象的内存空间的。
虚函数引入后类会发生一些变化。
2、虚函数表
虚函数表的生成时机和生成原因?
当类A中存在至少一个虚函数的时候,在编译期间,编译器就会为类A生成一个虚函数表(virtual table),这个虚函数表会一直伴随着类A.
经过编译、链接,直到生成一个可执行文件后,这个类A以及伴随类A的虚函数表都会保存在这个可执行文件中。
在可执行文件执行的过程中,也会被一并装置到内存中。
3、虚函数表指针
虚函数表指针被赋值的时机。
虚函数表指针vptr与虚函数表vtbl之间的关系?
对于有虚函数类A在编译的时候,编译器会向类A的构造函数中安插为vptr赋值的语句(编译期间做),是编译器默默在背后为程序员所做的事情。
当程序运行起来之后,当创建一个类A对象的时候,会执行类A的构造函数,因为构造函数中有给vptr赋值的语句,从而使vptr指向类A的vbtl。
4、类对象在内存中的布局
当生成一个类A对象的时候,就可以看一看类A对象在内存中的布局了。左侧就是类A对象的内存布局,内存布局中,编译器会向其中插入一个虚函数表指针vptr,还有两个int类型的成员变量。编译器会将vptr指向类A的虚函数表vtbl,这个是跟着类走的。vtbl中包含三个指针,这三个指针分别指向三个虚函数:vfunc、vfunc2、~A。func1、func2两个普通成员函数统统属于类A的组成部分,并不占用类A对象的内存空间。
12 = 虚函数表指针(4)+ int(4) + int(4)。
5、虚函数的工作原理和多态性的体现
常规的多态性的理解:简单来说,父类中有一个虚函数A,子类中也有一个同名虚函数A。当通过父类指针new一个子类对象,或者是通过父类引用来绑定一个子类对象的时候。如果用父类指针来调用这个虚函数,那么调用的其实是子类的虚函数。
关于多态这个概念,分两方面谈:代码实现上、表现形式上
不管谈到哪方面,谈到多态,必须存在虚函数,没有虚函数,决定不可能存在多态。
类中定义了虚函数,并且我们要调用虚函数,那么才存在多态性的可能。
5.1、代码实现方面
当调用一个虚函数的时候,可以看一下调用路线。是不是利用vptr找到vtbl,然后通过查询vtbl来找到虚函数表的入口地址,并去执行这个虚函数。如果走的是这个路线,那么就是多态。
示例代码:
5.2、表现形式方面
多态性的表现如下图
示例代码:
当有继承关系存在的时候,父类对象和子类对象他们的内存布局,以及父类与子类的虚函数表如下图所示:
假设父类Base有f、g、h三个虚函数,子类Derive中重写了Base类中的g虚函数。因为有虚函数存在,所以编译器就会向每一个类对象中插入一个vptr。Derive类对象中指向的是Derive类的虚函数表,Base类对象中指向的是Base类的虚函数表,虚函数表是归属于类的。子类Derive有从父类Base类中继承而来的虚函数f、h和g,g虚函数被重写过了,覆盖掉了父类的虚函数g。下图中粉色的地址指向了Derive自己的虚函数g,其余两个指针指向了父类的f、h虚函数。
假设父类Base有f、g、h三个虚函数,子类Derive中重写了Base类中的g虚函数