编译器在编译的时候,发现基类中有虚函数,此时编译器会为每个包含虚函数的类创建一个虚表(vtable),该表是一个一维数组,
在这个数组中存放每个虚函数地址,这张表解决了继承、覆盖的问题,保证其真实放映实际的函数。编译器会为基类与继承类都建
立一个虚表(即便继承类中没有virtual函数,但是基类中有,那么继承类也就是有)。一般编译器将指向虚表指针存放在实例对象
中第一个地址。那么其实我们只要获得这个类的实例对象的地址,就获得了指向该虚表的指针的地址。如图:
举例分析:
首先验证是否实例化的第一个地址是虚表,输出虚表第一个函数。
class base
{
public:
vritual void first( );
virtual void second( );
};
void base::first( )
{
std::cout<<"In base the first virtual function "<<std::endl;
}
void base::second( )
{
std::cout<<"In base the second virtual function "<<std::endl;
}
int main(int argc,char ** argv)
{
typedef void (*fun)(void);
fun f=NULL;
base b;
std::cout<<"vtptr address is "<<&b<<endl;
//这里b地址直接取的话,就是object了,需要通过类型转换获得其
//第一个地址,然后取该地址所指向的内容,在将其类容转换成地址
std::cout<<"vt address is "<<(int *)*(int *)&b<<std::endl;
//取虚表中的第一个地址
f=(fun)*(int *)*(int *)&b;
f( );
f=(fun)*( (int *)*(int *)&b + 1 );
f( );
std::cout<<"the class instance size is "<<sizeof(b)<<std::endl;
return 0;
}
运行结果
情况一:一般继承,无覆盖
class base
{
public:
vritual void first( );
virtual void second( );
};
void base::first( )
{
std::cout<<"In base the first virtual function "<<std::endl;
}
void base::second( )
{
std::cout<<"In base the second virtual function "<<std::endl;
}
class derived:public base
{
public:
void thrid( );
virtual void fourth( );
};
void derived::thrid( )
{
std::cout<<"In derived the thrid function "<<std::endl;
}
void derived::fourth( )
{
std::cout<<"In derived the fourth function"<<std::endl;
}
int main( )
{
derived d;
typedef void(*fun)(void);
std::cout<<"vtptr address is"<<&d<<std::endl;
std::cout<<"vt address is"<<(int *)*(int *)&d<<std::endl;
f=(fun)*(int *)*(int *)&b;
f( );
f=(fun)*( (int *)*(int *)&b + 1 );
f( );
f=(fun)*( (int *)*(int *)&b + 2 );
f( );
f=(fun)*( (int *)*(int *)&b + 3 );
f( );
return 0;
}
运行结果为:
由上图我们可以证明,不是虚函数的函数是绝对不会进入虚函数表的。
去掉下面两行代码即可:
f=(fun)*( (int *)*(int *)&b + 3 );
f( );
运行结果为:
情况二:一般继承有覆盖
class base
{
public:
vritual void first( );
virtual void second( );
};
void base::first( )
{
std::cout<<"In base the first virtual function "<<std::endl;
}
void base::second( )
{
std::cout<<"In base the second virtual function "<<std::endl;
}
class derived:public base
{
public:
void first( );
virtual void fourth( );
};
void derived::first( )
{
std::cout<<"In derived the first function "<<std::endl;
}
void derived::fourth( )
{
std::cout<<"In derived the fourth function"<<std::endl;
}
int main( )
{
derived d;
typedef void(*fun)(void);
std::cout<<"vtptr address is"<<&d<<std::endl;
std::cout<<"vt address is"<<(int *)*(int *)&d<<std::endl;
f=(fun)*(int *)*(int *)&b;
f( );
f=(fun)*( (int *)*(int *)&b + 1 );
f( );
f=(fun)*( (int *)*(int *)&b + 2 );
f( );
f=(fun)*( (int *)*(int *)&b + 3 );
f( );
return 0;
}
结果如下
这个结果说明超出了虚表的地址范围,虚表里只有三个虚函数,所以同上的操作,去掉下面代码
f=(fun)*( (int *)*(int *)&b + 3 );
f( );
在运行得
那么我们就可以得到如下结构图
虚表指针在什么时候和什么地方初始化呢?
在构造函数中进行虚表的创建和虚表指针的初始化。根据构造函数的调用顺序,在构造继承类构造函数时,先调用基类的构造函数,此时编译器只知道基类,并不知道后面继承类。它初始化基类对象的虚表指针,该虚表指针指向基类的虚表。当执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。
所以在在将各种继承类对象地址赋值给基类指针的时候,指向虚表的指针是不一样的,当调用其有覆盖的函数的时候,就能实现其多态效果。