【C++學習筆記】虛擬函式(一)
12.3 昨天居然斷更了,唉,寫部落格真是很需要毅力呀,更新上今天的學習筆記。
上次我們講到多型性的定義以及簡述了實現方式以及靜態編譯和動態編譯的概念。這次,我們來具體講一講虛擬函式。
什麼是虛擬函式
在某基類中宣告為 virtual 並在一個或多個派生類中被重新定義的成員函式,
用法格式為:virtual 函式返回型別 函式名(引數表) {函式體};實現多型性,通過指向派生類的基類指標或引用,訪問派生類中同名覆蓋成員函式。
解釋:普通的函式是在編譯時就已經確定好其功能並且不能改變了,而虛擬函式允許函式呼叫與函式體之間的聯絡在執行時才建立,也就是在執行時才決定如何動作,就是動態聯編。
我們做一個簡單的類比就是普通的函式是一個工具,它在使用前就已經確定了這個工具到底是錘子還是榔頭還是鐵鍬,而函式過載則是
把工具整合化做成一把瑞士軍刀,你想用這個工具做什麼,就能抽出相應的工具,函式過載開起來似乎很方便了,但是這把瑞士軍刀裡到底有什麼工具是定死了的,也就是說,**在編譯後,這把軍刀裡有哪些工具是無法改變的。**而虛擬函式是什麼呢?虛擬函式就是給你錢然後告訴你一個工具商店,我這個程式要使用一個工具,具體是什麼工具呢,我不知道,反正遇到了要呼叫這個工具的地方拿錢自己去商店買相應的工具就好了。這樣雖然每次使用工具都要專門跑到商店裡去買,效率低了,但保證了面對不同的問題我都有法子解決。
總之,虛擬函式是動態聯編的基礎。虛擬函式是成員函式,而且是非static的成員函式。
##12.11整整一星期沒寫了,太懶了自己
先放個栗子(標準用法)來說明這個問題吧:
#include<iostream.h> class base {public: virtual void who() {cout<<"this is the class of base!"<<endl;} virtual ~base{cout<<" the class of base is deleted"<<endl;} }; class derive1: public base {public: void who() {cout<<"this is the class of derive1!"<<endl;} ~derive1{cout<<" the class of derive1 is deleted"<<endl;} }; class derive2:public base {public: void who() //這裡who()是一個虛擬函式,virtual可加 {cout<<"this is the class of derive2!"<<endl;} //可不加。(注意點1) ~derive2{cout<<" the class of derive2 is deleted"<<endl;} }; int main{ base obj,*ptr; derive1 obj1; derive2 obj2; //---------------------------------------------------------------------------- ptr = &obj; ptr->who(); ptr = &obj1; //通過基類指標呼叫,此時才是動態聯編 ptr->who(); ptr = &obj2; ptr->who(); //---------------------------------------------------------------------------- obj.who(); obj1.who(); //通過“物件.成員函式”的形式呼叫,雖然達到了一樣的效果, obj.who(); //但在程式執行時已經是靜態呼叫了。(注意點3) }
虛擬函式的一些性質和注意點
- 通過定義虛擬函式來使用C++提供的多型機制時,派生類應該從它的基類公有派生。賦值相容規則成立的前提條件是派生類從其基類公有派生。(你要先規定工具是大家公用的,才能說明虛擬函式)
- 必須首先在基類中定義虛擬函式。在實際應用中,應該在類等級內需要具有動態多型性的幾個層次中的最高層類內首先宣告虛擬函式。
- 在派生類對基類中宣告的虛擬函式重新定義時,**關鍵字virtual可寫可不寫。**但在基類中的virtual關鍵字是必須要加的。
- 使用物件名和點運算子的方式可以呼叫虛擬函式,但這種呼叫在編譯時進行的是靜態聯編,並沒有充分運用靜態函式的特性。只有通過基類指標訪問虛擬函式時才能獲得執行時的多型。
- 一個虛擬函式無論被公有繼承多少次,它仍然保持其虛擬函式的特性。(該是虛的就是虛的,一旦基類定義了,後面怎麼做都改變不了它的性質。)
- 虛擬函式必須是其所在類的成員函式,而不能是友元函式,也不能是靜態成員函式。但是,虛擬函式可以在另一個類中被宣告為友元函式。(因為系統是依據特定的物件來決定該啟用哪個函式,所以要保證類與虛擬函式結構的一一對應,說白了,就是一個類要保證對應一個虛擬函式,這樣系統才知道該用哪個結構。)
- **行內函數不能是虛擬函式。**這個點很有意思,有人可能會問,在栗子中函式不是定義在類內嗎?怎麼行內函數就不能變成虛函數了呢?這是因為嚴格來說,行內函數和外聯函式不是通過是否在類內定義來區別的。在這裡,雖然虛擬函式是在類內定義的,但編譯時編譯器仍然把這個函式看作是非內聯的(氣不氣)。原因是行內函數不能再執行中動態確定其位置的,系統給了你在類內定義的形式,但實際上一但定義了是虛擬函式,它就不可能是行內函數。
- **建構函式不能是虛擬函式,但解構函式可以。**因為虛擬函式作為執行過程中的多型基礎,是以物件為核心的,而建構函式是用來構建物件的,也就是說先有物件,後有虛擬函式,所以建構函式絕對不可能是虛擬函式。但解構函式是用來銷燬物件的,是在物件後,所以可以是虛擬函式。
虛解構函式
解構函式的虛擬函式比較特別,它的函式名可以是不同的,解釋這個原因,我們要想想為什麼虛解構函式要出現。
我們想,前面提到虛擬函式的動態聯編要實現必須用基類的指標呼叫來實現,那現在假如我要用delete收回這個物件指標,可是無論其到底是基類指標還是派生類此時都是基類的指標,那系統怎麼判別用誰的解構函式呢?
此時就用到了解構函式的虛擬函式,解構函式的虛擬函式是一類函式,凡是派生類的解構函式都是基類虛擬函式的一個具體展現。這樣編譯器就能根據物件呼叫合適的析構函數了。