不用指定一個對象的確切類型,而能夠調用其成員函數的特性,即稱為“多態性”。“多態性”這個詞表示“能夠存取多種形態”,也就是能夠讓一個語句調用很多不同的函數。
9.4虛函數與繼承的關係
一個基類的成員函數在派生類中重新定義時,應該將其定義為虛函但事實上派生類中未必就會真的重新定義一個虛函數。如果派生類中未重新定義該虛函數,則基類中的虛函數便可直接為派生類所繼承,且在派生類中仍是虛函數。
類Child有一個成員函數Funcl(),由於與基類的虛函數Fund()定義完全相同,它是一個虛函數。同時,類Child還從基類繼承了兩個成員函數Fimc2()和Fimc3()。這兩個繼承來的成員函數在類Child中仍是虛成員函數。
9.5覆蓋函數與虛函數
我們先來看一個例子。
例9.12覆蓋函數與虛函數區別。
在派生類Child中,成員函數F
c(int)與基類的虛函數Fane()參數表不同,所以Child::Func(int)不是虛函數。實際上Child::Func(int)JfePatent::Func()的覆蓋函數。
與虛函數不同,Visual C++根據作用域的原則訪問覆蓋函數。也就是說,編譯器是根據變量的類型訪問覆蓋函數的。
在主函數main()中,雖然基類指針ptrpa實際指向派生類的對象,但是語句ptt-pa->Func()卻調用基類的成員函數Parent::Func()。
可見,訪問覆蓋函數時,Visual C++根本就不理會基類指針實際指向的對象類型。同樣道理,盡管基類引用tefPa實際引用了一個派生類對象,但是語句refpa.Func()還是調用基類的函數Parent:Func()。
上麵介紹的隻是非常簡單的情況,當覆蓋函數與虛函數混用時,很容易令人混淆不清。要注意以下幾點:
1.在基類和派生類中不要使用名字和參數表都相同的函數,除非使用虛函數。
2.提倡使用虛函數,避免使用覆蓋函數。
3.覆蓋函數、函數重載、虛函數這三個概念的界線本來就不是特別清楚,不必死摳概念,而應從實際運用的角度出發,攀握它們的實際用法,特別是重載和虛函數。
9.6對虛函數的進一步說明
一、顯式訪問
有時我們需要訪問基類的虛函數,而避免訪問派生類的虛函數,這時,可以使用作用域分辨符來指明所要訪問的虛函數。
在例9.13中,基類ComFinal用來表示學生期末考試公共科目的成。派生類Chemis-try表示化學專業的學生期末考試成績。兩個類中都定義了一個相同的函數Show(),用來顯示學生成績。由於Show()是一個虛函數,所以當被生類Chifetiy的Show()函數調用棊類的Show()時,必須使用作用域分辨符顯式訪問:
ComFinal::Show();
在主函數main()中,基類引用rcfCom真正引用的是派生類的對象,所以語句refCom.Show()調用派生類函數Chemistry:Show()。
但是,語句idLCom.ComFinal::Show()中使用了作用域分辨符,所以它將訪問基類的虛函數ComFinal::Show()。
要注意的是,在main()中顯式訪問基類的虛函數,隻是為了說明顯式訪問基類虛函數的方法,沒有任何意義。
二、純虛函數和抽象類
如果在基類中定義了一個什麼操作也不做的虛函數,僅僅是為了讓以後的派生類繼承該虛函數的虛屬性,最好的辦法是使用“純虛函數”。
在基類Parent中,Func()是一個純虛函數。
純虛函數不需要定義,它將在派生類中重新定義。在基類中,該函數隻是為派生類提供一個多態接口,並無其他作用。
包含有純虛函數的類叫“抽象類”。由於抽象類包含了沒有函數定義的純虛函數,所以,不能創建抽象類的對象。
例如下麵的語句是錯誤的:
Parentob;//出錯
由於Parent是抽象類,所以不能創建它的對象。
但是,我們可以說明抽象類的指針或引用。
例如下麵的語句是正確的:
Parent*ptr;Parent&ref=obch;//obch是派生類的對象我們可以使用抽象類的指針或引用來操縱派生類的對象。
如果某個派生類沒有給出基類的純虛函數的定義,該函數就被作為純虛函數繼承,因此該派生類也成為一個抽象類。對於普通虛函數來說,不會發生這種情況,因為當一個派生類省略了某個普通虛函數的定義時,可以使用基類中的虛函數。但對於純虛函數,派生類就不能使用基類中的純虛函數,因為基類中的純虛函數也沒有定義。