/Files/xkfz/為什麼採用虛函數virtual.ppt
C++中的繼承與虛函數各種概念
虛繼承與一般繼承
虛繼承和一般的繼承不同,一般的繼承,在目前大多數的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
{
public:
vitual void foo(){cout < <"foo from parent";};
void foo1(){cout < <"foo1 from parent";};
};
class son:public parent
{
void foo(){cout < <"foo from son";};
void foo1(){cout < <"foo1 from son";};
};
int main()
{
parent *p=new son();
p->foo();
p->foo1();
return 0;
}
其輸出結果是:
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;
參考:http://www.haogongju.net/art/1069038
http://www.diybl.com/course/3_program/c++/cppsl/2008520/117246.html
一個介紹虛函數的很好的ppt:pdf下載,ppt下載