一切結論都必須以事實爲依據,這樣才能造成長久記憶!java
虛表的造成過程:ios
1、對於非繼承類而言:編譯器會根據類中是否有虛函數產生虛表,若是有虛函數,則會造成虛表,虛表中會按照成員函數聲明順序存放函數的地址,從而造成存放函數入口地址的函數指針數組,最後把數組地址存放在類的開始的位置,只一個指針的大小。c++
2、對於繼承類而言:對於單繼承,若是父類中有虛函數表,則編譯器編譯時,會把父類的虛表賦值一份,並把新的地址放在類的開始位置,只佔一個指針大小的空間;對於多繼承,編譯器會把多個父類的虛表分別複製一份,並把新的虛表地址依次按照繼承順序存放在子類的開始位置。數組
3、對於繼承時,若是其重寫其父類的虛函數(注意重寫與重載的區別),編譯器會把新的函數的地址更新到原來虛函數對應的位置,若是該類新增虛函數是,編譯器會默認把新的虛函數的地址存放第一個虛表中,追加在其後。對於多重繼承時,重寫會覆蓋全部繼承虛表中被重寫的虛函數地址;新增的虛函數會追加在第一個虛表的內虛表項的後邊。函數
4、虛函數就是指成員函數前加virtual 關鍵字的函數,但構造函數和友元函數(不是類成員函數)不會是虛函數,其餘成員函數均可以使用virtual 修飾,固然純虛函數也是虛函數。spa
5、補充:在c++中純虛函數與純面向對象的語言,如java、C#,中的接口在功能上是一致的,都是起到接口規範的做用,只是在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 }
運行截圖: