虛函數是在類中被聲明為virtual的成員函數,當編譯器看到通過指標或引用調用此類函數時,對其執行晚綁定,即通過指標(或引用)指向的類的類型資訊來決定該函數是哪個類的。通常此類指標或引用都聲明為基類的,它可以指向基類或衍生類別的對象。
多態指同一個方法根據其所屬的不同對象可以有不同的行為。
下面來看一個例子:
程式運行輸出結果: 8, 12
為什麼Base 對象的大小是8個位元組而不是4個位元組,為什麼Derived 對象的大小是12個位元組而不是8個位元組,多出來的4個位元組做什麼用呢?和多態的實現有什麼關係?
每一個有虛函數的類(或有虛函數的類的衍生類別)都有一個虛函數表,該類的任何對象中都放著虛函數表的指標。虛函數表中列出了該類的虛函數地址。多出來的4個位元組就是用來放虛函數表的地址的。
每當建立一個包含虛函數的類或者從一個有虛函數的基類中派生一個類時,編譯器就為這個類建立一個VTABLE,在這個表中,放置了在這個類中或 它的基類中所有聲明為virtual的虛函數的地址。然後編譯器在這個類中放置VPTR指向相應的VTABLE。VPTR必須在建構函式中被初始化,在 VPTR初始化之前,絕對不能調用虛函數。所有的基類對象或者從基類派生出的對象的VPTR都在各自對象的相同位置。所有的VTABLE有相同的順序,不 管何種類型的對象。
C++的函數調用與C一樣,都是從右向左進棧的,其間,對象的首地址也即this指標的值被壓入棧,正因為調用每個成員函數時this都必須作 為參數壓進棧,所以成員函數知道它工作在哪個特殊對象上。這樣,我們總能看到,在成員函數調用之前壓棧的次數等於參數個數加一(除了STatic成員函 數,它沒有this)。
純虛函數 :
抽象類別就是在類的聲明前面加上virtual關鍵字,為了防止誤用抽象類別,可以在抽象類別中定義純虛函數,例如virtual void x()=0;
這樣做,等於告訴編譯器在VTABLE中為函數保留一個間隔,但在這個特定間隔不放地址,只要有一個純虛函數,則VTABLE就是不完全的,包含純虛函數的類稱為純抽象基類。
虛函數是C++中用於實現多態(polymorphism)的機制。核心理念就是通過基類訪問衍生類別定義的函數。假設我們有下面的類層次:
那麼,在使用的時候,我們可以:
A * a = new B();
a->foo(); // 在這裡,a雖然是指向A的指標,但是被調用的函數(foo)卻是B的!
這個例子是虛函數的一個典型應用,通過這個例子,也許你就對虛函數有了一些概念。它虛就虛在所謂“延遲聯編”或者“動態聯編”上,一個類函數的 調用並不是在編譯時間刻被確定的,而是在運行時刻被確定的。由於編寫代碼的時候並不能確定被調用的是基類的函數還是哪個衍生類別的函數,所以被成為“虛”函 數。
如下聲明表示一個函數為純虛函數:
class A
{
public:
virtual void foo()=0; // =0標誌一個虛函數為純虛函數
};
一個函式宣告為純虛後,純虛函數的意思是:我是一個抽象類別!不要把我執行個體化!純虛函數用來規範衍生類別的行為,實際上就是所謂的“介面”。它告訴使用者,我的衍生類別都會有這個函數。