C++虚函数与虚函数表

时间:2022-04-01 03:29:02

1、多态是C++三大特性之一,也是面向对象设计中一个非常重要的概念。所谓多态性就是当不同的

对象接收到相同的消息时所产生的不同的响应。

C++中虚函数的存在其实就是为了解决面向对象编程设计当中的多态问题,即通过基类的指针(或者是引用)

指向实例化的派生类对象,从而通过基类的指针(或者是引用)调用派生类的成员函数,从而实现晚绑定(

不在编译时根据函数名和参数来确定调用哪一个函数,而是在运行时根据具体的环境来动态的确定)。

正是由于虚函数这种机制的存在,把基类的析构函数申明为虚析构函数就能够解决通过释放基类指针所造成的

内存泄露的问题。

 

2、没有继承关系时的虚函数表

 1 #include <iostream>
2
3 class CBassClass {
4 public:
5 virtual void FunC1(void) { std::cout << "FunC1(void)" << std::endl; }
6 virtual void FunC2(void) { std::cout << "FunC2(void)" << std::endl; }
7 virtual void FunC3(void) { std::cout << "FunC3(void)" << std::endl; }
8 };
9
10 typedef void (*pfnFun) (void);
11
12 int main(void)
13 {
14 CBassClass *pBase = new CBassClass;
15
16 std::cout << "虚函数表地址:0x" << (int *)pBase << std::endl; // 虚函数表地址存在对象所占内存空间的前4个字节
17 std::cout << "虚函数表中的第一个函数地址:0x" << *((int *)(*(int *)pBase) + 0) << std::endl;
18 std::cout << "虚函数表中的第二个函数地址:0x" << *((int *)(*(int *)pBase) + 1) << std::endl;
19 std::cout << "虚函数表中的第三个函数地址:0x" << *((int *)(*(int *)pBase) + 2) << std::endl;
20 std::cout << "虚函数表中的结束标志:0x" << *((int *)(*(int *)pBase) + 3) << std::endl;
21
22 pfnFun fn = NULL;
23 for (int i = 0; i < 3; i++) {
24 fn = (pfnFun)*((int *)(*(int *)pBase) + i);
25 (*fn)();
26 }
27
28 delete pBase;
29 return 0;
30 }

以上代码的输出结果:

C++虚函数与虚函数表   

从结果可以看出来,虚函数的入口地址在虚函数表当中是按照顺序依次存放的,最后会以0作为结束标志。

 

3、存在继承关系的虚函数表(不存在覆盖关系时)

 1 #include <iostream>
2
3 class CBassClass {
4 public:
5 virtual void FunC1(void) { std::cout << "CBassClass FunC1(void)" << std::endl; }
6 virtual void FunC2(void) { std::cout << "CBassClass FunC2(void)" << std::endl; }
7 virtual void FunC3(void) { std::cout << "CBassClass FunC3(void)" << std::endl; }
8 };
9
10 class DClass :public CBassClass {
11 public:
12 virtual void FunC4(void) { std::cout << "DClass FunC4(void)" << std::endl; }
13 virtual void FunC5(void) { std::cout << "DClass FunC5(void)" << std::endl; }
14 };
15
16 typedef void (*pfnFun) (void);
17
18 int main(void)
19 {
20 CBassClass *pBase = new DClass; // DClass *pBase = new DClass; 结果一样
21
22 std::cout << "虚函数表地址:0x" << (int *)pBase << std::endl; // 虚函数表地址存在对象所占内存空间的前4个字节
23 std::cout << "虚函数表中的第一个函数地址:0x" << *((int *)(*(int *)pBase) + 0) << std::endl;
24 std::cout << "虚函数表中的第二个函数地址:0x" << *((int *)(*(int *)pBase) + 1) << std::endl;
25 std::cout << "虚函数表中的第三个函数地址:0x" << *((int *)(*(int *)pBase) + 2) << std::endl;
26 std::cout << "虚函数表中的第四个函数地址:0x" << *((int *)(*(int *)pBase) + 3) << std::endl;
27 std::cout << "虚函数表中的第五个函数地址:0x" << *((int *)(*(int *)pBase) + 4) << std::endl;
28 std::cout << "虚函数表中的结束标志:0x" << *((int *)(*(int *)pBase) + 5) << std::endl;
29
30 pfnFun fn = NULL;
31 for (int i = 0; i < 5; i++) {
32 fn = (pfnFun)*((int *)(*(int *)pBase) + i);
33 (*fn)();
34 }
35
36 delete pBase;
37
38 return 0;
39 }

以上代码运行的结果:

 1 虚函数表地址:0x0052B1C0
2 虚函数表中的第一个函数地址:0x16520113
3 虚函数表中的第二个函数地址:0x16520378
4 虚函数表中的第三个函数地址:0x16520383
5 虚函数表中的第四个函数地址:0x16520423
6 虚函数表中的第五个函数地址:0x16520418
7 虚函数表中的结束标志:0x0
8 CBassClass FunC1(void)
9 CBassClass FunC2(void)
10 CBassClass FunC3(void)
11 DClass FunC4(void)
12 DClass FunC5(void)

从上面的结果可以看出来,虚函数表中的函数地址是按顺序存放的,派生类继承了基类的虚函数表,然后

又在虚函数表中将自己的虚函数的函数地址存放在虚函数表中,先存放基类的虚函数后存放派生类的虚函数。

 

4、存在继承关系的虚函数表(存在覆盖关系时)

 1 #include <iostream>
2
3 class CBassClass {
4 public:
5 virtual void FunC1(void) { std::cout << "CBassClass FunC1(void)" << std::endl; }
6 virtual void FunC2(void) { std::cout << "CBassClass FunC2(void)" << std::endl; }
7 virtual void FunC3(void) { std::cout << "CBassClass FunC3(void)" << std::endl; }
8 };
9
10 class DClass :public CBassClass {
11 public:
12 virtual void FunC2(void) { std::cout << "DClass FunC2(void)" << std::endl; }
13 virtual void FunC3(void) { std::cout << "DClass FunC3(void)" << std::endl; }
14 virtual void FunC4(void) { std::cout << "DClass FunC4(void)" << std::endl; }
15 virtual void FunC5(void) { std::cout << "DClass FunC5(void)" << std::endl; }
16 };
17
18 typedef void (*pfnFun) (void);
19
20
21 int main(void)
22 {
23 CBassClass *pBase = new DClass;
24
25 std::cout << "虚函数表地址:0x" << (int *)pBase << std::endl; // 虚函数表地址存在对象所占内存空间的前4个字节
26 std::cout << "虚函数表中的第一个函数地址:0x" << *((int *)(*(int *)pBase) + 0) << std::endl;
27 std::cout << "虚函数表中的第二个函数地址:0x" << *((int *)(*(int *)pBase) + 1) << std::endl;
28 std::cout << "虚函数表中的第三个函数地址:0x" << *((int *)(*(int *)pBase) + 2) << std::endl;
29 std::cout << "虚函数表中的第四个函数地址:0x" << *((int *)(*(int *)pBase) + 3) << std::endl;
30 std::cout << "虚函数表中的第五个函数地址:0x" << *((int *)(*(int *)pBase) + 4) << std::endl;
31 std::cout << "虚函数表中的结束标志:0x" << *((int *)(*(int *)pBase) + 5) << std::endl;
32
33 pfnFun fn = NULL;
34 for (int i = 0; i < 5; i++) {
35 fn = (pfnFun)*((int *)(*(int *)pBase) + i);
36 (*fn)();
37 }
38
39 delete pBase;
40
41 return 0;
42 }

以上代码运行的结果是:

 1 虚函数表地址:0x006CB1C0
2 虚函数表中的第一个函数地址:0x267185
3 虚函数表中的第一个函数地址:0x267510
4 虚函数表中的第一个函数地址:0x267505
5 虚函数表中的第一个函数地址:0x267495
6 虚函数表中的第一个函数地址:0x267490
7 CBassClass FunC1(void)
8 DClass FunC2(void)
9 DClass FunC3(void)
10 DClass FunC4(void)
11 DClass FunC5(void)

从结果可以看出来,派生类中重写了基类中的两个虚函数,派生类继承了基类的虚函数表之后,将虚函数表中

相应的两个函数指针替换成了派生类自己的虚函数。其他的倒是没什么变化。

 

5、多继承情况

示例代码就不拷贝出来了,总之就是多重继承的情况,会为每一个基类建一个虚函数表。派生类的虚函数放到

第一个虚函数表的后面。

具体的请看这篇博客:  http://www.cnblogs.com/chinazhangjie/archive/2012/07/11/2586535.html