C++ 基础面试题
前言
在理解虚函数列v-table,我们要了解对象所内部地址结构,以及单一继承下和多继承下虚函数所执行过程,对此清晰理解函数所执行顺序。对于如何理虚函数表往往作为一道基础面试题会被经常提及。
一、什么是虚函数列表
C++中的虚函数的实现一般是通过虚函数表,类的虚函数表是一块连续的内存,每个内存单元中记录一个JMP指令的地址。
注意的是,编译器会为每个有虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象共享。类的每个虚成员占据虚函数表中的一行。如果类中有N个虚函数,那么其虚函数表将有N*4字节的大小。
虚函数(Virtual Function)是通过一张虚函数表来实现的。简称为V-Table。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。这样,在有虚函数的类的实例中分配了指向这个表的指针的内存,所以,当用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
虚函数与继承一起使用的,没有继承关系的类,虚函数就不没有存在意义。
1、在单一继承下有虚函数对象空间
//基础类
class BaseClass
{
private int x;
public virtual void func_1(){}
public virtual void func_2(){}
}
//继承类
class AClass :public BaseClass
{
private int y;
public virtual void func_1(){}
public virtual void func_3(){}
}
如下图:
类AClass的对象指针a所指向的空间
第一行为虚函数表的地址空间,C++规定,如果该类有虚函数,虚函数列表所在地址空间必须在对象地址空间第一行。
第二行为成员变量x的空间。
第三行为成员变量y的空间,如果有其他变量按照变量的声明顺序而定,以此类推。
因为AClass覆盖了基类的函数func_1函数,所有在虚函数列表中通过 a->func_1()调用的是AClass类的func_1,而不是基类的。占用的空间为所有成员变量和虚函数表地址空间的总和
共:12个Byte,哪怕基类的是私有成员,也子类继承后,声明的对象中也要占用其空间
2、在多继承情况下有虚函数的对象空间
//基础类1
class Base1Class
{
private int x;
public virtual void func_1(){}
public virtual void func_2(){}
}
//基础类1
class Base2Class
{
private int y;
public virtual void func_3(){}
public virtual void func_4(){}
public void funct_5(){}
}
class BaseClass :public Base1Class,public Base2Class
{
}
//子类
class AClass: public BaseClass
{
private int z;
public virtual void func_1(){}
public virtual void func_4(){}
public void funct_6(){}
}
如下图:
在多继承情况下具有虚函数的对象空间是有多张虚函数表,因为类AClass覆盖了函数func_1和func_4,所以依据单继承虚函数调用原则,a->func_1()和a->func_4()调用的是AClass类函数成员;func_2和func_3并没有重写也就没有覆盖,所使用是基类的相应函数
对象指针a所指向的对象所占用的内存空间为两张虚函数表以及成员变量所占用的空间公20个Byte。
三、函数调用查找顺序
子类对象访问相应接口时,调用访问顺序如下图:
//基础类1
class BaseClass
{
public virtual void func_1(){}
public void func_2(){}
}
//子类
class AClass :public BaseClass
{
public virtual void func_1(){}
}
//BaseClass 为a对象指针所声明的类对象指针,AClass为 a所实际指向实际类
BaseClass* a = new AClass();
如果调用a->func_1()时按照函数调用顺序原则,func_1()是被子类重写,从子类虚函数列表中查找,所以调用的是AClass::func_1();
如果调用的是a->func_2()不是虚函数,那么从对象所声明的类中查找,没有,再从基类中查找,从下往上一直找到为止。
总结
虚函数表是类共享,并不相对于对象,只不过对象空间有一个指向继承关系的虚函数表的地址。该地址在对象的空间的起始位置;所以所以sizeof(类)大小空间为除了成员变量并包含虚函数表*N地址空间。
如果没有继承关系虚函数就失去其意义。