1. 衍生類中基類的指標和引用
上一篇中,已經介紹了類的繼承;這篇中,將介紹繼承中另一個重要和實用的方面-虛擬函數。
在討論虛擬函數之前,我們應明確為什麼需要虛擬函數。之前我們知道,衍生類包含了基類的一部分和本身的一部分。
輸出:
因為一個Derive的對象也是Base類型,如老師是一個人。
輸出:
因為rBase和pBase是Base類型,只能看到Base類的成員或者是Base類繼承其他類的成員,對於Derived類的非繼承成員是不可見的。
另一個例子:
輸出:
2)在基類中使用指標和引用
如果沒有使用指標,對於多個動物的呼叫:
當然也可以這樣寫:
但是,Animal只會執行其對應的呼叫函數。
測試:
而且,當動物種類超出30種時,需要30個數組來儲存不同的動物種類。
這兩種方法都有個明顯的缺點:基類指標指向衍生類,執行的函數是基類的函數,而不是衍生類的重定義函數。
2. 虛擬函數
在第1節中,使用基類指標,可以簡化代碼;但是基類指標只能執行基類的對應函數,而不是衍生類的重定義函數。
例如:
輸出是:rBase is a Base。
這個問題可以通過虛擬函數來解決:
虛擬函數-特殊類型的函數,執行相同簽名函數的最進階衍生版本。
只需將virtual關鍵字放在函式宣告的前面。
修改上面的例子:
輸出是:rBase is a Derived。
因為使用virtual關鍵字,GetName執行力衍生類版本的函數;
複雜一點的例子:
輸出是:rBase is a C。
流程:首先rBase是一個A引用,因為A中的GetName是一個虛擬函數;然後執行遍曆A到C的相同簽名的函數,最後執行最進階衍生的C類,然後執行C的重定義函數。
因為rBase指向的是一個C對象,所以不會遍曆D的函數。
更複雜的例子:
輸出:
再次測試:
輸出:
注意:衍生類函數的簽名必須和基類的虛擬函數的簽名一致,才能達到執行衍生類函數的效果。
2)virtual關鍵字的使用
A)事實上,virtual關鍵字在衍生類中不必要使用的。
B)通常,最原始的基類使用virtual關鍵字,這樣所有繼承的衍生類都可以虛擬執行;
C)通常,推薦在衍生類中也使用virtual關鍵字,儘管文法上不需要;
3)虛擬函數的傳回值
在正規情況下,虛擬函數和衍生類的重定義函數必須有一致的傳回型別;
但是,例外:如果虛擬函數返回的是類的指標或引用,那麼重定義函數可以返回衍生類的指標或引用。
3. 虛擬解構函式、虛擬賦值、重寫虛擬列表
1)虛擬解構函式
C++提供了一個預設的解構函式,有時需自訂解構函式,特別是在動態分配記憶體。
例如:
這時,列印:Calling ~Base()。
如果採用虛擬解構函式:
此時,輸出是:
Calling ~Drived
Calling ~Base()
2)虛擬賦值
將賦值虛擬是可能的。不像虛擬析構,虛擬賦值是麻煩之源。
3)重寫虛擬化
使用全域運算子::來顯示執行基類函數:
這種做法是比較少使用,但只是說明是可能的。
虛擬函數的缺點:低效的,執行一個虛擬函數需要的時間更長;還要另外開闢記憶體空間。
4. 提前綁定和延遲綁定
當CPU執行編譯器時,每行語句被編譯成一行或多行的電腦代碼,每一行指定了唯一的順序地址;函數也一樣,每個函數結尾都有一個唯一的順序地址。
所謂綁定,就是將標誌符轉換為機器語言地址的過程;本章節主要講述函數的綁定。
1)提前綁定
當執行直接調用一個函數時,這個進程成為提前綁定(靜態繫結);記住,每個函數都有唯一機器地址,當編譯器遇到函數調用,編譯器將函數調用轉換為機器語言,CPU跳轉到函數的地址。
因為Add、Subtract和Multiply是直接函數調用,即提前綁定。
2)延遲綁定
在一些程式中,不可能知道那些函數直到運行時才載入,即延遲綁定(動態綁定);
在C++中實現消極式載入主要是採用函數指標;
修改上面的main函數:
因為pFcn知道運行時才確定執行的調用函數,所以稱作延遲綁定。
延遲綁定效率相對低一點,因為要涉及更多的間接調用級數;但延遲更靈活,因為函數調用知道運行時才確定。
5. 虛擬表
在應用虛擬函數,C++使用了延遲綁定的特殊形式-虛擬表。
虛擬表是延遲管理器管理的一張函數調用列表。名字如:“vtable”, “virtual functiontable”, “virtual method table”, or “dispatch table”。
虛擬表是編譯器在編譯時間建立的一個靜態數組;每個函數調用都有一個入口供對象調用。
編譯器為基類添加類一個隱藏的指標: *__vptr;這是個真實的指標,衍生類可以繼承。
例如:
編譯器建立3張虛擬表,記錄每個類的虛擬函數;
每個虛擬表的*_vptr負責管理函數調用的遍曆;
通過虛擬表,編譯器和程式更準確地找到所需的虛擬函數。
6. 純粹虛擬函數、抽象基類和介面類
這是虛擬函數的最後一節,恭喜各位通過了C++語言最難懂的部分了。
1)純粹虛擬(抽象)函數和抽象基類
目前為止,所看到的虛擬函數都有函數體(定義體),C++允許建立一類特殊的虛擬函數-純粹虛擬函數(抽象函數),沒有函數體。
使用一個純粹的虛擬函數有2個主要的結果:
A)任何類使用了一個或多個的純粹虛擬函數,成為抽象基類;抽象類別-即不可以執行個體化;
B)任何繼承於抽象基類的衍生類必須實現該函數;
一個純粹的虛擬函數是非常有用的,我們想將一個函數放到基類,衍生類只需知道其傳回型別即可;這樣就預防了衍生類忘記實現該函數;
2)介面類
一個介面類-不包含任何成員變數,所有函數都是純粹虛擬函數。
介面是非常有用的,當你想定義衍生類必須實現的功能;但功能的實現細節依賴於具體的衍生類。
介面類越來越流行,因為其易用、易擴充和易維護。
【免責特此聲明:
1)本內容可能是來自互連網的,或經過本人整理的,僅僅代表了互連網和個人的意見和看法!
2)本內容僅僅提供參考,任何參考該內容造成任何的後果,均與原創作者和本部落格作者無關!】