我們知道通過一個指向之類的父類指針可以調用子類的虛方法,因為子類的方法會覆蓋父類同樣的方法,通過這個指針可以找到對象實例的地址,通過實例的地址可以找到指向對應方法表的指針,而通過這個方法的名字就可以確定這個方法在方法表中的位置,直接調用就行,在多繼承的時候,一個類可能有多個方法表,也就有多個指向這些方法表的指針,一個類有多個父類,怎麼通過其中一個父類的指針調用之類的虛方法?
其實前面幾句話並沒有真正說清楚,在單繼承中,父類是怎麼調用子類的虛方法的,還有多繼承又是怎麼實現這點的,想知道這些,請認真往下看。
我們先看單繼承是怎麼實現的。先上兩個簡單的類:
#include <iostream> ~ <<<< SetA(= B: ~ <<<< <<<< (*Fun)(<<<<** pVtab0 = (**)& ( i=; (Fun)pVtab0[][i]!=NULL; i++= (Fun)pVtab0[<< <<i<<<<* b1=&<<<<** pVtab1 = (**)& ( i=; (Fun)pVtab1[][i]!=NULL; i++= (Fun)pVtab1[<< <<i<<<<<<<<** pVtab2 = (**)&* ( i=; (Fun)pVtab2[][i]!=NULL; i++= (Fun)pVtab2[<< <<i<<<<<<<<&b<<<<<<b1<<endl<<
運行結果如下:
通過運行結果我們知道:通過父類指向子類的指針調用的是子類的虛方法。在單一繼承中,雖然父類有父類的虛方法表,子類有子類的虛方法表,但是子類並沒有指向父類虛方法的指針,在子類的實例中,子類和父類是公用一個虛方法表,當然只有一個指向方法表的指針,為什麼可以公用一個虛方法表呢,虛方法表的第一個方法是析構函數,子類的方法會覆蓋父類的同樣的方法,子類新增的虛方法放在虛方法表的後面,也就是說子類的虛方法表完全覆蓋父類的虛方法表,即子類的每個虛方法與父類對應的虛方法,在各種的方法表中的索引是一樣的。
但是在多繼承中就不是這樣了,第一個被繼承的類使用起來跟單繼承是完全一樣的,但是後面被繼承的類就不是這樣了,且仔細往下看。
還是先上3個簡單的類
#include <iostream> ~ <<<< ~ <<<< <<<< C: A, ~ GetB() <<<< <<<< <<<< (*Fun)(* c=* a=* b=<<<<(C)<<endl<<<<<<c<<<<<<a<<<<<<b<<endl<<endl<<<<<<** pVtab1 = (**)&* ( i=; (Fun)pVtab1[][i]!=NULL; i++= (Fun)pVtab1[<< <<i<<<<&*pFun<< <<endl<<<<<<= (**)&* ( i=; (Fun)pVtab1[][i]!=NULL; i++= (Fun)pVtab1[<< <<i<<<<&*pFun<<
運行結果如下:
從結果說話:
Sizeof(C)=20,我們並不意外,在單繼承的時候,父類和子類是公用一個指向虛方法表的指針,在多繼承中,同樣第一個父類和子類公用這個指針,而從第二個父類開始就有自己單獨的指針,其實就是父類的實例在子類的內存中保持完整的結構,也就是說在多重繼承中,之類的實例就是每一個父類的實例拼接而成的,當然可能因為繼承的復雜性,會加一些輔助的指針。
指針a與指針c指向同一個地址,即c的首地址,而b所指的地址與a所指的地址相差8字節剛好就是類A實例的大小,也就是說在C的內存布局中,先存放了A的實例,在存放B的實例,sizeof(B)=8(字段int b和指向B虛方法表的指針),在家上C自己的字段int c剛好是20字節。
讓我有點意外的是:方法B::SB,C::GetB並沒有出現在類C的方法表中,而且C::GetB是C覆寫B中的GetB方法,怎麼沒有出現在C的方法表中呢?在《深入探索C++對象模型》一書中講到,這兩個方法同時應該出現在C的方法表中,同樣也會覆蓋B的虛方法表。可能是不通的編譯器有不同的實現,我用的是VS2010,那本書上講的是編譯器cfront
OK,我們不用管不同的編譯器實現上的區別,這點小區別無傷大雅,虛方法的調用機制還是一樣的。
先來分析幾個小例子,看看虛方法的實現機制。