1. 程式人生 > >C++學習第13篇-虛擬函式

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)介面類

一個介面類-不包含任何成員變數,所有函式都是純粹虛擬函式。

介面是非常有用的,當你想定義衍生類必須實現的功能;但功能的實現細節依賴於具體的衍生類。


介面類越來越流行,因為其易用、易擴充套件和易維護。