虛表的造成

一切結論都必須以事實爲依據,這樣才能造成長久記憶!java

虛表的造成過程:ios

1、對於非繼承類而言:編譯器會根據類中是否有虛函數產生虛表,若是有虛函數,則會造成虛表,虛表中會按照成員函數聲明順序存放函數的地址,從而造成存放函數入口地址的函數指針數組,最後把數組地址存放在類的開始的位置,只一個指針的大小。c++

2、對於繼承類而言:對於單繼承,若是父類中有虛函數表,則編譯器編譯時,會把父類的虛表賦值一份,並把新的地址放在類的開始位置,只佔一個指針大小的空間;對於多繼承,編譯器會把多個父類的虛表分別複製一份,並把新的虛表地址依次按照繼承順序存放在子類的開始位置。數組

3、對於繼承時,若是其重寫其父類的虛函數(注意重寫與重載的區別),編譯器會把新的函數的地址更新到原來虛函數對應的位置,若是該類新增虛函數是,編譯器會默認把新的虛函數的地址存放第一個虛表中,追加在其後。對於多重繼承時,重寫會覆蓋全部繼承虛表中被重寫的虛函數地址;新增的虛函數會追加在第一個虛表的內虛表項的後邊。函數

4、虛函數就是指成員函數前加virtual 關鍵字的函數,但構造函數和友元函數(不是類成員函數)不會是虛函數,其餘成員函數均可以使用virtual 修飾,固然純虛函數也是虛函數。spa

5、補充:在c++中純虛函數與純面向對象的語言,javaC#,中的接口在功能上是一致的,都是起到接口規範的做用,只是在C++中純虛函數在類中定義,因此致使在C++,繼承能夠是多繼承方式,而在純面向對象中,只能是單繼承。.net

對於以上的結論的圖像化的表示,圖形方式的表示更直觀一點,你們能夠參考http://blog.csdn.net/haoel/article/details/1948051。3d

可是關於這些論點的論證,但願你們參考個人代碼。指針

"朝花夕拾",爲了論證以前結論,本身再次驗證一下:code

注意:

①要確認當前的主機的位數以及編譯器。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 }

 運行截圖:

相關文章
相關標籤/搜索