虚表的形成

一切结论都必须以事实为依据,这样才能形成长久记忆!

虚表的形成过程:

一、对于非继承类而言:编译器会根据类中是否有虚函数产生虚表,如果有虚函数,则会形成虚表,虚表中会按照成员函数声明顺序存放函数的地址,从而形成存放函数入口地址的函数指针数组,最后把数组地址存放在类的开始的位置,只一个指针的大小。

二、对于继承类而言:对于单继承,如果父类中有虚函数表,则编译器编译时,会把父类的虚表赋值一份,并把新的地址放在类的开始位置,只占一个指针大小的空间;对于多继承,编译器会把多个父类的虚表分别复制一份,并把新的虚表地址依次按照继承顺序存放在子类的开始位置。

三、对于继承时,如果其重写其父类的虚函数(注意重写与重载的区别),编译器会把新的函数的地址更新到原来虚函数对应的位置,如果该类新增虚函数是,编译器会默认把新的虚函数的地址存放第一个虚表中,追加在其后。对于多重继承时,重写会覆盖所有继承虚表中被重写的虚函数地址;新增的虚函数会追加在第一个虚表的内虚表项的后边。

四、虚函数就是指成员函数前加virtual 关键字的函数,但构造函数和友元函数(不是类成员函数)不会是虚函数,其他成员函数都可以使用virtual 修饰,当然纯虚函数也是虚函数。

五、补充:在c++中纯虚函数与纯面向对象的语言,javaC#,中的接口在功能上是一致的,都是起到接口规范的作用,只是在C++中纯虚函数在类中定义,所以导致在C++,继承可以是多继承方式,而在纯面向对象中,只能是单继承。

对于以上的结论的图像化的表示,图形方式的表示更直观一点,大家可以参考http://blog.csdn.net/haoel/article/details/1948051。

但是关于这些论点的论证,希望大家参考我的代码。

"朝花夕拾",为了论证之前结论,自己再次验证一下:

注意:

①要确认当前的主机的位数以及编译器。64位机指针长度8byte,32位机指针长度为4byte.

②注意内存对齐机制,这个机制以为着,申明3byte内存的类大小:sizeof(类名) = 4.关于内存对齐和c语言结构体的对齐方式是一致的。

③空类的大小为1byte,原因是被编译器插进去的一个char ,使得这个class的不同实体(object)在内存中配置独一无二的地址,这样产生的对象的地址才不同。

④继承的时候,无论虚函数的的访问修饰符,和继承的方式如何都可以通过虚表进行调用。

⑤继承的时候,复制虚表本身,并没有复制虚表项中函数指针所指的函数的函数体,即虚函数在内存中的定义只有一份。

 

 以下是验证内容:

结论一:

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 class A
 6 {
 7 
 8 };
 9 class B
10 {
11     virtual void  fun1()
12     {
13         cout <<"fun1" <<endl;
14     }
15     virtual void  fun2()
16     {
17         cout <<"fun2" <<endl;
18     }
19 };
20 
21 int main()
22 {
23     //空类大小为1
24     cout << sizeof(A) <<endl;
25     //指针大小为系统位数
26     cout << sizeof(new A()) << endl;
27     A t_a;
28     //对象也是1
29     cout << sizeof(t_a) << endl;
30 
31 
32     cout << "******************" <<endl;
33     
34     //两个虚函数,放在一个虚函数表里,最后将虚表的入口地址存放在类开始位置。
35     //指针大小为系统位数
36     cout << sizeof(B) <<endl;
37     //指针大小为系统位数
38     cout << sizeof(new B()) << endl;
39     B t_b;
40     //指针大小为系统位数
41     cout << sizeof(t_b) << endl;
42 
43     cout << "*******************" << endl;
44     //指向对象的地址,指向对象的开始位置
45     int *p = (int *)&t_b;
46      //指向虚表的第一项
47     int *p_fun =  (int *)*p;
48     //指向虚表的第二项,
49     //sizeof(void *)/sizeof(int) 而不使用1的原因是:
50     //在不同位数的系统中,指针的加减的单位是取决于其指针的类型
51     //如在32位系统中,int型指针+1,可以指向第二个指针。
52     //当时在64位系统中,如果+1,此时指针移动的是sizeof(int)byte的位置,此时指向的不是第二个指针的位置,因为指针大小为8byte。所以使用sizeof(void *)/sizeof(int) 获得每个指针占用几个单位的int型空间。
53     int *p_fun2 = p_fun + sizeof(void *)/sizeof(int);
54     //将两项内的数据,即函数指针,强转。得到函数指针
55     void (*f1)()  = (void (*) (void))*p_fun;
56     void (*f2)()  = (void (*) (void))*p_fun2;
57     //执行函数指针
58     f1();
59     f2();
60     //打印的时候,需要转换成int型指针才能正常打印
61     cout << (int *)f1 <<endl;
62     cout << (int *)f2 <<endl;
63     
64 
65 #if 0
66     //这种方式,编译器报错
67     cout << &(t_b.fun1) << endl;
68     
69 #endif
70 
71     return 0;
72 }

运行结果:

 

结论二:

 

  1 #include <iostream>
  2 
  3 using namespace std;
  4 
  5 class A
  6 {
  7 public:
  8     virtual void a1()
  9     {
 10         cout << "a1" <<endl;
 11     }
 12     virtual void a2()
 13     {
 14         cout << "a2" << endl;
 15     }
 16 };
 17 class AA
 18 {
 19     
 20     virtual void aa1()
 21     {
 22         cout << "aa1" <<endl;
 23     }
 24     virtual void aa2()
 25     {
 26         cout << "aa2" << endl;
 27     }
 28 };
 29 class B:public A
 30 {
 31     
 32 };
 33 
 34 class C:public A,public AA
 35 {
 36     
 37 };
 38 
 39 int main()
 40 {
 41     cout << "*****************单继承验证**********************"<<endl;
 42     cout << sizeof(A) <<endl;
 43     cout << sizeof(B) <<endl;
 44 
 45     //证明①子类中的虚表与父类虚表入口地址不同
 46     A t_a;
 47     int * pa = (int *)&t_a;
 48     int *pa1 = (int *)*pa;
 49     cout << "父类虚表地址:" << pa1 <<endl;
 50 
 51     B t_b;
 52     int * pb = (int *)&t_b;
 53     int * pb1 = (int *)*pb;
 54     int * pb2 = pb1 + sizeof(void *)/sizeof(int);
 55     cout << "子类虚表地址:" << pb1 << endl;
 56     //结论①:结果地址不同。
 57 
 58     //证明②: 子类中仍然可以通过虚函数调用父类的方法,
 59     void (*f1)()  = (void (*) (void))*pb1;
 60     void (*f2)()  = (void (*) (void))*pb2;
 61     
 62     f1();
 63     f2();
 64     //结论②:子类可以通过新的虚表访问父类的虚函数
 65 
 66     //最终的结论:单继承的时候,是在内存中复制一份虚表,并将新的地址放在子类开始位置
 67     
 68     cout << "****************多继承验证*********************"  <<endl;
 69     //对于多继承,编译器会把多个父类的虚表分别复制一份,并把新的虚表地址依次按照继承顺序存放在子类的开始位置。
 70     AA t_aa;
 71     int *paa = (int *)&t_aa;
 72     int *paa1 = (int *)*paa;
 73 
 74     cout << "父类A的虚表地址:" << pa1 << endl;
 75     cout << "父类AA的虚表地址:" << paa1 << endl;
 76     
 77     C t_c;
 78     int *pc1 = (int *)&t_c;
 79     //继承A虚表的入口地址
 80     int *pc1_1 = (int *)*pc1;
 81     //继承AA虚表的入口地址
 82     int *pc2 = pc1 + sizeof(void *)/sizeof(int);
 83     int *pc2_1 = (int*)*pc2;
 84 
 85     cout << "子类中获得第一项虚表地址:" << pc1_1 << endl;
 86     cout << "子类中获得第二项虚表地址:" << pc2_1 << endl;
 87     
 88     //验证上述地址确实是copy后的新的虚表地址
 89     cout << "通过继承子类A的虚表执行虚函数" <<endl;
 90     int *pc1_2 = pc1_1 + sizeof(void *)/sizeof(int);
 91     void (*fc1_1)()  = (void (*) (void))*pc1_1;
 92     void (*fc1_2)()  = (void (*) (void))*pc1_2;
 93 
 94     fc1_1();
 95     fc1_2();
 96     
 97     
 98     cout << "通过继承子类AA的虚表执行虚函数" <<endl;
 99     int *pc2_2 = pc2_1 + sizeof(void *)/sizeof(int);
100     void (*fc2_1)()  = (void (*) (void))*pc2_1;
101     void (*fc2_2)()  = (void (*) (void))*pc2_2;
102 
103     fc2_1();
104     fc2_2();
105 
106     return 0;
107 }  

 运行截图:

 

结论三:

 

  1 #include <iostream>
  2 
  3 using namespace std;
  4 
  5 class A
  6 {
  7     public:
  8         virtual void a1()
  9         {
 10             cout << "a1" <<endl;
 11         }
 12         virtual void a2()
 13         {
 14             cout << "a2" << endl;
 15         }
 16 };
 17 class AA
 18 {
 19 
 20     virtual void a1()
 21     {   
 22         cout << "aa1" <<endl;
 23     }   
 24     virtual void aa2()
 25     {   
 26         cout << "aa2" << endl;
 27     }   
 28 };
 29 class B:public A
 30 {
 31 
 32 };
 33 
 34 class BB:public A
 35 {
 36     //重写A的虚函数,注意不是重载,重载不是一个函数,只是函数名相同罢了
 37     virtual void a1()
 38     {
 39         cout << "bb1" <<endl;
 40     }
 41     virtual void a2()
 42     {
 43         cout << "bb2" << endl;
 44     }
 45     //新增虚函数的时候,会将新增的虚函数地址作为新的表项,追加在原虚表的后边
 46     virtual void b3()
 47     {
 48         cout << "bb3" << endl;
 49     }
 50 };
 51 
 52 class C:public A,public AA
 53 {
 54     //重写A的虚函数,注意不是重载,重载不是一个函数,只是函数名相同罢了
 55     virtual void a1()
 56     {
 57         cout << "cc1" <<endl;
 58     }
 59     virtual void a2()
 60     {
 61         cout << "cc2" << endl;
 62     }
 63     //新增虚函数的时候,会将新增的虚函数地址作为新的表项,追加在原虚表的后边
 64     virtual void b3()
 65     {
 66         cout << "cc3" << endl;
 67     }
 68 };
 69 
 70 
 71 int main()
 72 {
 73     A t_a;
 74     int * pa = (int *)&t_a;
 75     int *pa1 = (int *)*pa;
 76     //cout << "父类虚表地址:" << pa1 <<endl;
 77 
 78     B t_b;
 79     int * pb = (int *)&t_b;
 80     int * pb1 = (int *)*pb;
 81     int * pb2 = pb1 + sizeof(void *)/sizeof(int);
 82     //cout << "子类虚表地址:" << pb1 << endl;
 83 
 84     void (*f1)()  = (void (*) (void))*pb1;
 85     void (*f2)()  = (void (*) (void))*pb2;
 86 
 87     f1();
 88     f2();
 89     //结论②:子类可以通过新的虚表访问父类的虚函数
 90 
 91 
 92     BB t_bb;
 93     int * pbb = (int *)&t_bb;
 94     int * pbb1 = (int *)*pbb;
 95     int * pbb2 = pbb1 + sizeof(void *)/sizeof(int);
 96     int * pbb3 = pbb2 + sizeof(void *)/sizeof(int);
 97     //cout << "子类虚表地址:" << pb1 << endl;
 98 
 99     void (*fb1)()  = (void (*) (void))*pbb1;
100     void (*fb2)()  = (void (*) (void))*pbb2;
101     void (*fb3)()  = (void (*) (void))*pbb3;
102     cout << "***********************单继承的情况****************************" << endl;
103 
104     cout  << "验证重写的时候,覆盖原虚表中被重写的虚函数的入口地址" << endl;
105     fb1();
106     fb2();
107     cout  << "验证新增虚函数的时候,追加在虚表已有表项的后边" << endl;
108     fb3();
109 
110     
111     cout << "***********************多继承的情况****************************" << endl;
112     //验证多重继承的时候,重写的虚函数地址会覆盖所有虚表中中被重写的虚函数地址,
113     //验证多重继承的时候,新增虚函数会自动追加到第一个继承虚表的后边。不会再追加到继承的其他虚表的虚表项。
114     
115     C t_c;
116     //虚表1    
117     int * pc1 = (int *)&t_c;
118     //虚表1的表项1
119     int * pc1_1 = (int *)*pc1;
120     //虚表1的表项2
121     int * pc1_2 = pc1_1 + sizeof(void *)/sizeof(int);
122     //虚表1的表项3
123     int * pc1_3 = pc1_2 + sizeof(void *)/sizeof(int);
124     
125     //虚表2
126     int * pc2 = pc1 + sizeof(void *)/sizeof(int);
127     //虚表2的表项1
128     int * pc2_1 = (int *)*pc2;
129     //虚表2的表项2
130     int * pc2_2 = pc2_1 + sizeof(void *)/sizeof(int);
131     //虚表2的表项3
132     int * pc2_3 = pc2_2 + sizeof(void *)/sizeof(int);
133 
134     //重写A的虚函数1
135     void (*fc1_1)()  = (void (*) (void))*pc1_1;
136     //重写A的虚函数2
137     void (*fc1_2)()  = (void (*) (void))*pc1_2;
138     //追加新的虚函数3
139     void (*fc1_3)()  = (void (*) (void))*pc1_3;
140     
141     
142     //重写AA的虚函数1,因为A和AA的第一个虚函数是相同的
143     void (*fc2_1)()  = (void (*) (void))*pc2_1;
144     //继承AA的虚函数2
145     void (*fc2_2)()  = (void (*) (void))*pc2_2;
146     //追加新的虚函数3,段错误,只追加到第一个虚表的后边,当前是虚表2,没有追加,如果访问就段错误
147     void (*fc2_3)()  = (void (*) (void))*pc2_3;
148     
149     cout << "重写A的虚函数1" << endl;
150     fc1_1();
151     cout << "重写A的虚函数2" << endl;
152     fc1_2();
153     cout << "追加虚表1的虚函数" << endl;
154     fc1_3();
155 
156 
157     cout << "重写AA的虚函数1" << endl;
158     fc2_1();
159     cout << "继承AA的虚函数2" << endl;
160     fc2_2();
161     //cout << "追加虚表1的虚函数" << endl;
162     //fc2_3();
163 
164     return 0;
165 }

 运行截图:

转载于:https://www.cnblogs.com/cz-blog/p/4264277.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值