C++學習第13篇-虛擬函式
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)介面類
一個介面類-不包含任何成員變數,所有函式都是純粹虛擬函式。
介面是非常有用的,當你想定義衍生類必須實現的功能;但功能的實現細節依賴於具體的衍生類。
介面類越來越流行,因為其易用、易擴充套件和易維護。