(2012-05-08 23:36:58)
虛函式是物件導向程式設計語言裡乙個很重要的機制,下面我們以乙個c++例子,分析其對應的c語言程式來說明虛函式的機制。
物件導向有了乙個重要的概念就是物件的例項,物件的例項代表乙個具體的物件,故其肯定有乙個資料結構儲存這例項的資料,這一資料報括變數,介面函式指標,如果是虛函式,則有相應的虛函式指標,其他函式指標不包括。
要講虛函式機制,必須講繼承,因為只有繼承才有虛函式的動態繫結功能,先講下c++繼承物件例項記憶體分配基礎知識:
c++繼承分為兩種,普通繼承和虛擬繼承(virtual)。具體的繼承又根據父類中的函式是否virtual而不同。
下面就單繼承分為幾種情況闡述:
1.普通繼承+父類無virtual函式
若子類沒有新定義virtual函式 此時子類的布局是 :
由低位址->高位址 為父類的元素(沒有vptr),子類的元素(沒有vptr).
若子類有新定義virtual函式 此時子類的布局是 :
由低位址->高位址 為父類的元素(沒有vptr),子類的元素(包含vptr,指向vtable.)
2. 普通繼承+父類有virtual函式
不管子類沒有新定義virtual函式 此時子類的布局是 : 由低位址->高位址 為父類的元素(包含vptr), 子類的元素.
如果子類有新定義的virtual函式,那麼在父類的vptr(也就是第乙個vptr)對應的vtable中新增乙個函式指標.
3.virtual繼承
若子類沒有新定義virtual函式 此時子類的布局是 :
由低位址->高位址 子類的元素(有vptr),虛基類的元素.為什麼這裡會出現vptr,因為虛基類派生出來的類中,虛類的物件不在固定位置(猜測應該是在記憶體的尾部),需要乙個中介才能訪問虛類的物件.所以雖然沒有virtual函式,子類也需要有乙個vptr,對應的vtable中需要有一項指向虛基類.
若子類有新定義virtual函式 此時子類的布局是與沒有定義新virtual函式記憶體布局一致.但是在vtable中會多出新增的虛函式的指標.
4.多重繼承
此時子類的布局是 :
由低位址->高位址 為父類p1的元素(p1按照實際情況確定元素中是否包含vptr), 父類p2的元素(p2按照實際情況確定元素中是否包含vptr),子類的元素.
如果所有父類都沒有vptr,那麼如果子類定義了新的virtual function,那麼子類的元素中會有vptr,對應的vtable會有相應的函式指標.
如果有的父類存在vptr.如果子類定義了新的virtual function,會生成乙個子類的vtable,這個子類的vtable是,在它的父類的vtable中後新增這個新的虛函式指標生成的.因為子類分配的空間顯示並沒有新增加乙個4位元組的指標空間,其實不管子類增加了多少新的虛函式,其空間大小不變,因為其和虛函式相關的分配的空間就是乙個vptr,是乙個指標,也就是4位元組,不變,要變是變在vtable.
fun1() {};
public virtual a();
public virtual b(int b);
int a;
}class test2 extends test1;
public virtual b(int b) ;
public vitrual c()」)}
int b;
}int main()
首先我們看看下:類test1,test2例項大小及其記憶體分配圖:
test1的例項資料大小是:虛函式表指標(4)+iaptr介面指標+int變數大小(4)=12
而test2的例項資料大小是:test1大小+其變數b大小=12+4=16
注意這是上面的提到的虛類繼承,子類新增的虛函式不增加子類大小,只是在其虛函式表中體現。
大家注意上面的test1,test2的建構函式,析構函式,fun1,fun2都沒加進去。
下面看下例項資料記憶體分布圖:
下面看下其對應的c語言偽**;
1. 已實現的函式:
test1.b(sturct test1 *this ,b);
test2.b(sturct test2 *this ,b)」)}
//上面是虛函式實現
test1.fun1(sturct test1 *this,){};
test2.fun2(sturct test2 *this){};
//這個是普通函式,就是上面的,只不過變了名字而已。
2.會生成類對應的結構體:
struct test1test1;
struct test2test2;
3.會生成兩個虛函式表結構體:
struct test1_vtbl
struct test1_vtbl test1_vtb1=;
//父類test1虛函式表。
struct test2_vtbl
struct test2_vtbl test2_vtb1=;
//子類test2虛函式表。
//注意虛函式a還是父類的a,因為其沒有過載,而b過載了就是test2的b了,同時析構函式也是虛函式,是自動加的。
4.編譯器自動生成的一些函式,建構函式,析構函式:
test1.test1(sturct test1 *this )
test1.~test1(sturct test2 *this);
test2.test2(sturct test2 *this)
test2.~test2(struct test2 *this);
5.main函式對應的**:
int main()
對應c偽**:
int main()
我們現在分析a->vtbl->b(a,1) 是如何呼叫到test2.b()函式的。
執行1後,虛函式表是空的,即為null;
執行2
test1 a=new test2();
a. fun1();
}其對應於fun1(tes1 this);
如果乙個x函式是虛函式,執行x才會呼叫this虛函式表中的x,如果是this.x,就直接繫結test.x函式了。
本篇文章**於:開發學院
C 多型呼叫和繼承記憶體分布
以下面 為例 class a virtual void fa void faa class b virtual void fb class c public a void fa void faa void main 首先c c 建立乙個c型別的物件c,類c是繼承a的。在vs2012中除錯,在監視中檢...
C 物件繼承後的記憶體分布
1.如果父類的純虛函式沒有實現,在沒有使用的的情況下 沒有new 或者直接生成物件 編譯不會報未定義。最近將乙個類物件指標直接轉換為void 儲存到了vector中,使用時再用static cast轉換為對應的父類指標,發現在多繼承的情況下這樣會有問題。原因是此物件有多個父類,static cast...
C 多型 繼承多型
什麼是多型?個人理解為 在程式語言繼承關係中,子類能替代父類,表現出不同的行為。換句話說 在繼承關係中,乙個類被例項化被其子類替代,子類中有父類的虛方法重寫,或者有父類同名方法 new 呼叫相同方法時候,將表現出子類或者父類中不同行為 老闆,上 static void main string arg...