建设工程竣工规划局网站,高端企业门户网站建设服务公司,做网站阳泉,德城区城乡建设局网站虚函数
对C 了解的人都应该知道虚函数#xff08;Virtual Function#xff09;是通过一张虚函数表#xff08;Virtual Table#xff09;来实现的。简称为V-Table。在这个表中#xff0c;主是要一个类的虚函数的地址表#xff0c;这张表解决了继承、覆盖的问题#xff0…虚函数
对C 了解的人都应该知道虚函数Virtual Function是通过一张虚函数表Virtual Table来实现的。简称为V-Table。在这个表中主是要一个类的虚函数的地址表这张表解决了继承、覆盖的问题保证其容真实反应实际的函数。这样在有虚函数的类的实例中这个表被分配在了这个实例的内存中所以当我们用父类的指针来操作一个子类的时候这张虚函数表就显得由为重要了它就像一个地图一样指明了实际所应该调用的函数。
这里我们着重看一下这张虚函数表。C的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下。 这意味着我们通过对象实例的地址得到这张虚函数表然后就可以遍历其中函数指针并调用相应的函数。
虚函数表存在的位置
由于虚函数表是由编译器给我们生成的那么编译器会把虚函数表安插在哪个位置呢下面可以简单的写一个示例来证明一下虚函数表的存在以及观察它所存在的位置先来看一下代码
#include iostream
#include stdio.h
using namespace std;class A{
public:int x;virtual void b() {}
};int main()
{A* p new A;cout p endl;cout p-x endl;return 0;
}定义了一个类A含有一个x和一个虚函数实例化一个对象然后输出对象的地址和对象成员x的地址我们想一下如果对象的地址和x的地址相同那么就意味着编译器把虚函数表放在了末尾如果两个地址不同那么就意味着虚函数表是放在最前面的。运行结果为16进制然后我们把它转为10进制观察一下 可以观察到结果是不同的而且正好相差了4个字节由此可见编译器把生成的虚函数表放在了最前面
获取虚函数表
既然虚函数表是真实存在的那么我们能不能想办法获取到虚函数表呢其实我们可以通过指针的形式去获得因为前面也提到了我们可以把虚函数表看作是一个数组每一个单元用来存放虚函数的地址那么当调用的时候可以直接通过指针去调用所需要的函数就行了。我们就类比这个思路去获取一下虚函数表。首先先定义两个类一个是基类一个是派生类代码如下
#include iostream
#include stdio.h
using namespace std;class Base {
public:virtual void a() { cout Base a() endl; }virtual void b() { cout Base b() endl; }virtual void c() { cout Base c() endl; }
};class Derive : public Base {
public:virtual void b() { cout Derive b() endl; }
};int main()
{cout -----------Base------------ endl;Base* q new Base;long* tmp1 (long*)q;long* vptr1 (long*)(*tmp1);for (int i 0; i 3; i) {printf(vptr[%d] : %p\n, i, vptr1[i]);}Derive* p new Derive;long* tmp (long*)p;long* vptr (long*)(*tmp);cout ---------Derive------------ endl;for (int i 0; i 3; i) {printf(vptr[%d] : %p\n, i, vptr[i]);}return 0;
}可见基类中的三个指针分别指向a,b,c虚函数而派生类中的三个指针中第一个和第三个和基类中的相同那么这就印证了上述我们所假设的情况那么这也就是虚函数表。但是仅仅只是观察指向的地址还不是让我们观察的特别清楚那么我们就通过定义函数指针来调用一下这几个地址看看结果是什么样的下面直接上代码
#include iostream
#include stdio.h
using namespace std;class Base {
public:virtual void a() { cout Base a() endl; }virtual void b() { cout Base b() endl; }virtual void c() { cout Base c() endl; }
};class Derive : public Base {
public:virtual void b() { cout Derive b() endl; }
};int main()
{typedef void (*Func)();cout -----------Base------------ endl;Base* q new Base;long* tmp1 (long*)q;long* vptr1 (long*)(*tmp1);for (int i 0; i 3; i) {printf(vptr[%d] : %p\n, i, vptr1[i]);}Func a (Func)vptr1[0];Func b (Func)vptr1[1];Func c (Func)vptr1[2];a();b();c();Derive* p new Derive;long* tmp (long*)p;long* vptr (long*)(*tmp);cout ---------Derive------------ endl;for (int i 0; i 3; i) {printf(vptr[%d] : %p\n, i, vptr[i]);}Func d (Func)vptr[0];Func e (Func)vptr[1];Func f (Func)vptr[2];d();e();f();return 0;
}多重继承的虚函数表
虚函数的引入其实就是为了实现多态(对于多态看到了一篇很不错的博客传送门)现在来研究一下多重继承的虚函数表是什么样的首先我们先来看一下简单的一般继承的代码
class Base1 {
public:virtual void A() { cout Base1 A() endl; }virtual void B() { cout Base1 B() endl; }virtual void C() { cout Base1 C() endl; }
};class Derive : public Base1{
public:virtual void MyA() { cout Derive MyA() endl; }
};那么我们现在在Derive中再添加一个虚函数让它覆盖基类中的虚函数代码如下
class Base1 {
public:virtual void A() { cout Base1 A() endl; }virtual void B() { cout Base1 B() endl; }virtual void C() { cout Base1 C() endl; }
};class Derive : public Base1{
public:virtual void MyA() { cout Derive MyA() endl; }virtual void B() { cout Derive B() endl; }
};这个是单继承的情况然后我们看看多重继承也就是Derive类继承两个基类先看一下代码
class Base1 {
public:virtual void A() { cout Base1 A() endl; }virtual void B() { cout Base1 B() endl; }virtual void C() { cout Base1 C() endl; }
};class Base2 {
public:virtual void D() { cout Base2 D() endl; }virtual void E() { cout Base2 E() endl; }
};class Derive : public Base1, public Base2{
public:virtual void A() { cout Derive A() endl; } // 覆盖Base1::A()virtual void D() { cout Derive D() endl; } // 覆盖Base2::D()virtual void MyA() { cout Derive MyA() endl; }
};首先我们明确一个概念对于多重继承的派生类来说它含有多个虚函数指针对于上述代码而言Derive含有两个虚函数指针所以它不是只有一个虚函数表然后把所有的虚函数都塞到这一个表中为了印证这一点我们下面会印证这一点首先我们先来看看这个多重继承的图示 #include iostream
#include stdio.h
using namespace std;class Base1 {
public:virtual void A() { cout Base1 A() endl; }virtual void B() { cout Base1 B() endl; }virtual void C() { cout Base1 C() endl; }
};class Base2 {
public:virtual void D() { cout Base2 D() endl; }virtual void E() { cout Base2 E() endl; }
};class Derive : public Base1, public Base2 {
public:virtual void A() { cout Derive A() endl; } // 覆盖Base1::A()virtual void D() { cout Derive D() endl; } // 覆盖Base2::D()virtual void MyA() { cout Derive MyA() endl; }
};int main()
{typedef void (*Func)();Derive d;Base1 b1 d;Base2 b2 d;cout Derive对象所占的内存大小为 sizeof(d) endl;cout \n---------第一个虚函数表------------- endl;long* tmp1 (long*)d; // 获取第一个虚函数表的指针long* vptr1 (long*)(*tmp1); // 获取虚函数表Func x1 (Func)vptr1[0];Func x2 (Func)vptr1[1];Func x3 (Func)vptr1[2];Func x4 (Func)vptr1[3];x1(); x2(); x3(); x4();cout \n---------第二个虚函数表------------- endl;long* tmp2 tmp1 1; // 获取第二个虚函数表指针 相当于跳过4个字节long* vptr2 (long*)(*tmp2);Func y1 (Func)vptr2[0];Func y2 (Func)vptr2[1];y1(); y2();return 0;
}因为在包含一个虚函数表的时候含有一个虚函数表指针所占用的大小为4个字节那么这里输出了8个字节就说明Derive对象含有两个虚函数表指针。然后我们通过获取到了这两个虚函数表并调用其对应的虚函数可以发现输出的结果和上面的示例图是相同的因此就证明了上述所说的结论是正确的。
1. 每一个基类都会有自己的虚函数表派生类的虚函数表的数量根据继承的基类的数量来定。
2. 派生类的虚函数表的顺序和继承时的顺序相同。
3. 派生类自己的虚函数放在第一个虚函数表的后面顺序也是和定义时顺序相同。
4. 对于派生类如果要覆盖父类中的虚函数那么会在虚函数表中代替其位置。
虚函数指针和虚函数表的创建时机
对于虚函数表来说在编译的过程中编译器就为含有虚函数的类创建了虚函数表并且编译器会在构造函数中插入一段代码这段代码用来给虚函数指针赋值。因此虚函数表是在编译的过程中创建。
对于虚函数指针来说由于虚函数指针是基于对象的所以对象在实例化的时候虚函数指针就会创建所以是在运行时创建。由于在实例化对象的时候会调用到构造函数所以就会执行虚函数指针的赋值代码从而将虚函数表的地址赋值给虚函数指针。