有图有真相带你理解C++中虚函数调用机制
高手请绕道,本文给初学者,本来想好好排版一下,实在被****这编辑器搞的没脾气了,排版可能有点乱
高手请绕道,本文献给初学者。
一、虚函数
在面向对象的高级语言中,实现多态机制,必不可少的重要一部分就是虚函数。在C++中,当一个类中含有virtual修饰的成员函数时,这个类和普通的类将有一些细微差别。我们先来看一个简单的例子.
/*初学者可以忽视这行(排除字节对齐引起的测试干扰)*/
#pragma pack(1)//开始
class NormalClass{
int data;
void fun(){}
};
class VirtualClass{
int data;
public:
virtual void fun(){}
};
#pragma pack()//结束
void TestPolymorphsim6(void)
{
cout << "sizeof(NormalClass)" << sizeof(NormalClass) << endl;
cout << "sizeof(VirtualClass)" << sizeof(VirtualClass) << endl;
}
输出结果下:
sizeof(NormalClass)4
sizeof(VirtualClass)8
通过上面的测试,我们发现含有虚函数的类,其大小比普通类多了4个字节。那么这4个字节到底存放的是什么呢?答案是这4个字节存放的是一个指针,这个指针指向一张虚函数表。
二、虚函数表
在C++中,当类中包含虚函数成员时,编译器会为这个类生成一张虚函数表。这个表可以理解成一个数组,数组的每个元素是每个虚函数的入口地址。而我们的类每次实例化一个对象时,系统都会为这个对象多分配4个字节,这4个字节存放的就是指向虚函数表的指针。我们来看个简单的例子:
/*初学者可以忽视这行(排除字节对齐引起的测试干扰)*/
#pragma pack(1)//开始
class VirtualClass{
int data;
public:
VirtualClass():data(0x11223344){
}
virtual void fun1(){}
virtual void fun2(){}
};
#pragma pack()//结束
void TestPolymorphsim6(void)
{
VirtualClass obj;
cout << sizeof(obj) << endl;
}
程序输出如下:
8
我们可以看到,当我们定义了多个虚函数后,我们的对象大小仍旧是8,所以不管虚函数有多少个,它都不会影响对象的大小,在对象的内存里面只存储一个虚表指针,虚函数的个数只会影响虚表的大小。
好的,上面的截图展示了含有虚函数的类,实例化对象后,对象的内存模型,我们也确实看到了4个字节的虚表指针,我们也看到了这张虚表,它涵有2个元素。分别是fun1和fun2.下面我们要来看下当使用这个类来派生一个子类时的情况。毕竟多态是建立在继承的继承上,没有继承谈不上多态。
#pragma pack(1)//开始
class VirtualClass{
int data;
public:
VirtualClass():data(0x11223344){
}
virtual void fun1(){}
virtual void fun2(){}
};
class SubClass : public VirtualClass{//子类为空
};
#pragma pack()//结束
void TestPolymorphsim6(void)
{
VirtualClass obj1;
SubClass obj2;
cout << sizeof(obj2) << endl;
}
我们定义了一个子类,这个子类继承VirtualClass,但是没有重写父类的虚函数,我们观察一下两个对象的内存
现在我们来重写父类的虚函数,fun1(), 因为多态一个重要的条件就是重写。不重写就没什么意思了,重写后的代码如下:
class VirtualClass{
int data;
public:
VirtualClass():data(0x11223344){
}
virtual void fun1(){}
virtual void fun2(){}
};
class SubClass : public VirtualClass{//子类为空
void fun1(){}
};
#pragma pack()//结束
void TestPolymorphsim6(void)
{
VirtualClass obj1;
SubClass obj2;
cout << sizeof(obj2) << endl;
}
我们再来看下两个对象的内存模型。
上图蓝色框中有两个错别字 不是已经存放父类的fun2, 是依旧存放父类的fun2,
最后我们来总结一下:通过父类指针或者引用来调用虚函数时,多态是怎么实现的: