很長時間都沒寫過博客了,主要是還沒有養成思考總結的習慣,今天來一發。 我是重度拖延症患者,本來這篇總結應該是早就應該寫下來的。 一、虛函數表 C++虛函數的機制想必大家都清楚了。不清楚的同學請參看各種C++入門書籍。這裡,我要討論一下這個虛函數機制究竟是怎麼實現的。虛函數主要是靠一張VTABLE來實現的,先來看看這個VTABLE在哪裡。 首先我們看下面的代碼: 復制代碼 1 class ClassA 2 { 3 public: 4 int m_data1; 5 int m_data2; 6 void vfunc1(){cout << "i am A" << endl;} 7 }; 8 class ClassB : public ClassA 9 { 10 public: 11 int m_data3; 12 void funcB(){} 13 void vfunc1(){cout << "i am B" << endl;} 14 }; 15 class ClassC : public ClassB 16 { 17 public: 18 int m_data1; 19 int m_data4; 20 void funcC(){} 21 void vfunc1(){cout << "i am C" << endl;} 22 }; 23 int main() 24 { 25 ClassA a; 26 ClassB b; 27 ClassC c; 28 cout << sizeof(int) << endl; 29 cout << sizeof(ClassA) << endl; 30 cout << sizeof(ClassB) << endl; 31 cout << sizeof(ClassC) << endl; 32 33 cout << &(a) << endl; 34 cout << &(a.m_data1) <<endl; 35 cout << &(a.m_data2) <<endl; 36 cout << endl; 37 38 cout << &(b) << endl; 39 cout << &(b.ClassA::m_data1) << endl; 40 cout << &(b.ClassA::m_data2) << endl; 41 cout << &(b.m_data3) <<endl; 42 cout << endl; 43 44 cout << &(c) << endl; 45 cout << &(c.ClassA::m_data1) << endl; 46 cout << &(c.m_data2) << endl; 47 cout << &(c.m_data3) << endl; 48 cout << &(c.m_data1) <<endl; 49 cout << &(c.m_data4) <<endl; 50 51 return 0; 52 } 復制代碼 我如果把上面的程序中ClassA的函數vfunc1聲明成虛函數,即將第6行改為: 1 virtual void vfunc1(){cout << "i am A" << endl;} 程序運行的兩個結果分別為: 由上面的結果可以明顯的看出,聲明為虛函數的類比原來的類在大小上多了4個字節。沒有虛函數的類的起始地址和第一個成員變量的地址保持一致,有虛函數的類的起始地址在第一個成員變量地址的前四個字節。這中間多出來的這四個字節就是隱藏起來的VPTR。VPTR是一個指向一個VTABLE的指針,換句話說,這多出來的四個字節裡面存的是VTABLE的地址。 而VTABLE裡面就記錄了這個類裡面虛函數的地址。 再看下面的代碼: 1 ClassA *pa; 2 ClassB *pb; 3 ClassC *pc; 4 pa = &c; 5 pa->vfunc1(); 我們都知道如果是虛函數,上面的代碼結果肯定為 i am C 如果沒用虛函數,結果為 i am A 這是怎麼做到的? 首先,我們要知道,子類繼承父類,子類擁有所有父類的成員變量跟成員函數,就是說: 1 c.vfunc1(); 2 c.ClassA::vfunc1() 我們可以上面的方式顯示地去訪問被子類覆蓋掉的函數和變量。可以理解為,雖然名字一樣,其實子類裡面有兩個獨立的vfunc1()函數,只不過子類調用的默認為ClassC::vfunc1()函數。 當我們用父類的指針去指向一個子類的指針時,會有一個向上轉換(我暫時這麼叫)的過程。用pa指向對象c時,pa是一個ClassA類型的指針,pa只能訪問ClassA類裡面有的成員變量和成員函數地址,多余的,A類沒有而C類有的成員變量和函數地址都被“upcasting”掉了。 沒有VTABLE時,只能找到ClassA類的vfunc1()函數的地址,找不到ClassC類的vfunc1()函數的地址。有虛函數表的存在時,對象c的虛函數表裡面會記錄ClassC::vfunc1()的地址,這樣用pa指向對象c時,虛函數表不會被“upcasting”掉,於是,按照虛函數表裡面的地址,就能夠成功訪問ClassC::vfunc1()。