虛繼承與一般繼承
虛繼承和一般的繼承不同,一般的繼承,在目前大多數的c++編譯器實現的物件模型中,派生類物件會直接包含基類物件的字段。而虛繼承的情況,派生類物件不會直接包含基類物件的字段,而是通過乙個間接的指標去訪問基類物件中的字段。
繼承的特性
繼承是物件導向中引入的重要特性之一,它的乙個重要的特點就是子類是父類,父類不是子類。也就是說:
1. 如果基類指標指向子類物件(pbase=&pchild),則該指標只能呼叫基類定義了的函式;(因為這個地方是靜態繫結,而靜態繫結所依賴的就是指標宣告時的型別)
2. 如果子類指標指向基類物件(pchild=(child *)pbase),則會出問題;(因為子類中可能有一些方法是基類中沒有的,在編譯時靜態繫結一些子類中特有的方法可能導致執行時沒有這個方法)
3. 如果基類和子類定義了同名的函式,則到底呼叫什麼函式,必須視該指標的原始型別而定,而不是視指標實際指向的物件型別而定。(這個地方其實就是一般繼承和虛函式繼承的區別:一般繼承靜態繫結,所以依據指標宣告的原始型別,而虛函式是進行動態繫結的,它是根據實際指標所指向的物件。)
4. 就算子類繼承了父類的某個函式(而未改寫它),該函式依然被視為父類的,該函式依然屬於父類的域中,該函式中使用的普通函式(非虛函式)依然被視為父類的函式。
5. 私有變數的繼承性:私有變數對子類是不可見的,即使是子類從父類繼承下來了,也仍然是不可見的,它僅僅能被父類的函式(沒有在子類中改寫或重寫過的函式)操作。
6. this指標:類的成員函式的引數中有乙個隱藏的引數this指標,這保證了被繼承的成員函式歸屬物件的正確性和無混淆性。
虛函式的實現原理
正是因為繼承的這個特點,虛函式的加入似乎就是順理成章的事情了。虛函式簡化並明確了軟體和各種類庫的設計以及維護。
一般的函式在編譯鏈結時就進行了繫結,這稱之為早繫結。由於資訊量不夠,所以只能依賴於呼叫它的物件或指標的宣告型別實現繫結。也就是侯俊傑說的,如果基類和子類定義了同名的函式,那麼到底呼叫哪個類的函式,必須視該指標的原始型別而定,而不是視指標實際指向的物件型別而定。因為賦值的動作還沒有產生。
而虛函式則不是這樣。虛函式實現的機制是晚繫結,它在編譯鏈結時並沒有與某個物件繫結----這也正是虛函式能實現多型(以相同**呼叫不同函式)的原因所在。當編譯器對程式進行編譯碰到虛函式時,將不會賦予乙個位址,而是插入一段彙編**。每個包含虛函式的類都會由編譯器產生乙個虛函式表和乙個虛函式表指標,其中虛函式表指標放在每個類的首位址處(也許不是,不過反正位址偏移量在每個類所佔記憶體中是固定的,這個在其他文章中有專門詳述虛函式表)。當程式執行時,碰到對虛函式的呼叫,則通過插入的彙編**到當前類的位址中找到虛函式表指標,通過虛函式的序號找到需要呼叫的虛函式。注意,乙個系列的類的虛函式表中某乙個函式的序號是一樣的。而且,編譯器會保證在使用父類指標操作子類物件時只能在父類已有的虛函式上實現虛函式的機制。
這裡還有乙個虛函式的預設引數的問題。虛函式是動態繫結的,而預設引數則是靜態繫結的,所以在虛函式中使用預設引數可以說是不符合邏輯的。如果子類改寫了父類虛函式中的預設引數,當使用多型特性時,會出現呼叫子類的虛函式,使用的卻是父類中的對應虛函式的預設引數的情況。
虛函式適用的兩種場合
1. 某個子類中呼叫繼承下來的非虛函式中有對已改寫的虛函式的呼叫。
值得注意的是,在某個子類中呼叫繼承下來的未改寫的非虛函式中有對已改寫的虛函式的呼叫時,呼叫的是當前子類中改寫過的虛函式;若該非虛函式中有對已改寫的非虛函式的呼叫時,呼叫的是父類的非虛函式(也是因為晚繫結)。這是mfc的慣用手法。
2.使用向上對映(父類指標=子類指標),實現**的重用
3.父類中的析構函式。當乙個類確信不會成為任何類的父類時,它的析構函式是不需要設定成虛函式的;當乙個類肯定會成為某個類的父類時,虛析構函式是必要的。因為若是父類中的析構函式是非虛的,則當用乙個父類的指向子類的指標delete子類時,這種行為在c++標準中並沒有被定義,是十分危險的。
繼承中的介面及其實現
經過以上分析可知,虛函式實際上就是繼承中的一種介面。繼承中一共有純虛函式、非純虛函式和非虛函式三種介面,它們在子類中的處理如下:
1.純虛函式:所有子類必須強制性地改寫,否則會報錯。這是一種僅僅繼承介面的方法。
2.非純虛函式:又被稱為簡單虛函式,可以在基類中有自己的實現(預設的動作),子類不一定要改寫,這是一種繼承介面及其預設實現的方法。
3.非虛函式:子類最好不要改寫,這是一種強制性地繼承介面及其實現的方法,表示的是一種共性。
當在同乙個類中存在同名但是引數不同的函式,叫作overloading(過載);子類改寫父類的虛函式,叫做overriding(覆蓋);子類改寫父類的非虛函式,叫做redefining(重定義),這是不推薦的。
虛函式、純虛函式、虛基類、抽象類、虛函式繼承、虛繼承-------各種概念解釋
虛函式:
虛函式是c++中用於實現多型(polymorphism)的機制。核心理念就是通過基類訪問派生類定義的函式。是c++中多型性的乙個重要體現,利用基類指標訪問派生類中的成員函式,這種情況下使用虛函式,這種情況下採用的是動態繫結技術。
虛函式必須是基類的非靜態成員函式,其訪問許可權可以是protected或public,在基類的類定義中定義虛函式的一般形式:
virtual 函式返回值型別 虛函式名(形參表)
動態繫結:
基類指標是呼叫派生類的中的成員函式還是呼叫基類中的成員函式要到程式執行時確定。主要要看指標所指向的物件。
純虛函式:
純虛函式是在基類中宣告的虛函式,它在基類中沒有定義,但要求任何派生類都要定義自己的實現方法。在基類中實現純虛函式的方法是在函式原型後加「=0」
virtual void funtion1()=0
虛基類、抽象類:
包含純虛函式的類稱為抽象類。由於抽象類包含了沒有定義的純虛函式,所以不能定義抽象類的物件。
虛函式繼承:
虛函式繼承就是覆蓋。即基類中的虛函式被派生類中的同名函式所覆蓋。 是實現多型的方法。
view code
class parent
;void foo1();
};class son:public parent
;void foo1();
};int main()
其輸出結果是:
foo from son,foo1 from parent
虛繼承:
解決多重繼承中派生類成員函式呼叫模糊問題。比如類a中有乙個函式print(),類b繼承a,類c繼承a,類d繼承類b和類c,這個時候,類d中就有兩個print函式,乙個是從b繼承得到的,乙個是從c繼承得到的,則類d的物件呼叫print函式就會出現print模糊的編譯錯誤。解決辦法:類b虛擬繼承a。類c虛擬繼承a,類d繼承b,c時,只拷貝a中的資料成員和函式成員一次,再遇到拷貝時候就忽略了!
虛繼承就是為了節約記憶體的,他是多重繼承中的特有的概念。適用與菱形繼承形式。
如:類b、c都繼承類a,d繼承類b和c。為了節省記憶體空間,可以將b、c對a的繼承定義為虛擬繼承,此時a就成了虛擬基類。
class a;
class b:public vitual a;
class c:public vitual a;
class d:public b,public c;
C 中的繼承與虛函式各種概念
c 中的繼承與虛函式各種概念 虛繼承與一般繼承 虛繼承和一般的繼承不同,一般的繼承,在目前大多數的 c 編譯器實現的物件模型中,派生類物件會直接包含基類物件的字段。而虛繼承的情況,派生類物件不會直接包含基類物件的字段,而是通過乙個間接的指標去訪問基類物件中的字段。繼承的特性 繼承是物件導向中引入的重...
C 中的多型公有繼承與虛函式
繼承中的 is a 關係 派生類和基類之間的特殊關係式基於c 繼承的底層模型的。實際上,c 有三種繼承方式 公有繼承 保護繼承 私有繼承。公有繼承是最常用的一種方式,它建立一種is a關係,即派生類物件也是乙個基類物件,可以對基類物件執行的任何操作,也可以對派生類物件執行。用 is a kind o...
繼承中的虛函式與非虛函式
在看公司 時,發現了一處關於虛函式的我比較難以理解的地方,大致描述如下 子類繼承父類,包括繼承了虛函式和非虛函式 子類呼叫父類中的非虛函式base printword 在這個非虛函式裡它又呼叫了虛函式doprintword 實驗表明呼叫的虛函式執行的是重寫的子類虛函式。include using n...