在c++中,繼承的概念可以理解爲c中得嵌套結構體,對於各種函數,類中的成員函數,類中的友元函數,各種繼承的虛擬函數,只要從編譯器的角度去理解
就會變得簡單。例如下例:
class D{
public:
int func_D();
int v_func_DD();
};
class D1 : public D{
public:
int func_D1();
virtual int v_func_DD();
};
class D2 : public D1{
public:
int func_D2();
int v_func_DD();
int d1;
};
//....
函數的具體實現略
//....
int main(int argc, char **argv){
D2 dd2;
D *d_ptr;
D1 *d1_ptr;
D2 *d2_ptr;
d_ptr = &dd2;
d1_ptr = &dd2;
d2_ptr = &dd2;
d_ptr->v_func_DD();//①
d1_ptr->v_func_DD();//②
d1_ptr->func_D2();//③
d2_ptr->func_D2();//④
return 0;
}
在主函數中,只有一個對象實例,即class D2類型的dd2;D2類直接繼承自D1,間接繼承自D,因爲可以用這兩種類型的指針引用D2類型的對象實例。
從編譯器角度來看時,對於①處,編譯器將做如下處理:
1.判斷指針類型,發現d_ptr的類型是class D。
2. 在class D中尋找 v_func_D函數的定義,如果沒有定義,將報錯。
3. 如果有一個定義,判斷是不是虛函數。
3.1 如果不是虛函數(正如本例那樣),則在全局符號表中找到相應的修飾後符號,假設爲v_func_DD_classD_void,並根據符號對應的函數地址調用該函數。
3.2 如果在class D中v_func_DD被定義爲虛函數,則需要動態綁定。
於是,在①處,由於v_func_DD並沒有在class D中被定義爲虛擬函數,則編譯器將判斷該調用爲int D::v_func_DD(),這裏與調用一個全局函數並無區別。
對於②處的函數調用,編譯器將會做出相同處理:
1.判斷指針類型,發現d1_ptr的類型是class D1。
2. 在class D1中尋找 v_func_D函數的定義,如果沒有定義,將報錯。
3. 如果有一個定義,判斷是不是虛函數。
3.1 如果不是虛函數,則在全局符號表中找到相應的修飾後符號,此時爲虛函數,跳過此步。
3.2 在class D1中v_func_DD被定義爲虛函數,則需要動態綁定,在動態綁定時,由於d1_ptr綁定的對象是D2類型,而此時D2類型的虛函數表已經建立好,並填入了
int D2::v_func_DD()的地址,所以這裏調用的真正函數是int D2::v_func_DD()。
對於③處的處理,編譯器判斷指針類型d1_ptr爲class D1,而D1中並沒有func_D2的定義,即是d1_ptr綁定的是D2對象,編譯器也是不允許這種情況的,所以編譯器將報
未定義的錯誤。
對於④的處理,由於指針是class D2類型,因此會正確調用int D2::func_D2()函數,這是,調用該成員函數和調用一個全局函數的情況並無區別,只不過編譯器根據類型名,
參數名,和函數名對最後生成的全局符號進行了mangling。