虛函數是在基類中聲明的成員函數,並由派生類重新定義(重寫)。當使用指針或對基類的引用來引用派生類對象時,可以為該對象調用虛擬函數並執行該派生類的函數版本。
- 虛函數可確保為對象調用正確的函數,而不管用於函數調用的引用(或指針)的類型如何。
- 它們主要用於實現運行時多態
- 函數在基類中使用虛擬關鍵字聲明。
- 函數調用的解析在運行時完成。
Rules for Virtual Functions
- 虛函數不能是靜態的,也不能是另一個類的朋友函數。
- 應該使用指針或基類類型的引用來訪問虛擬函數,以實現運行時多態。
- 虛擬函數的原型在基類和派生類中都應相同。
- 它們始終在基類中定義,而在派生類中被覆蓋。並非強製派生類重寫(或重新定義虛擬函數),在這種情況下,將使用函數的基類版本。
- 一個類可能具有虛擬析構函數,但不能具有虛擬構造函數。
Compile-time(早期綁定)與虛擬函數的運行時(後期綁定)行為
考慮以下顯示虛擬函數運行時行為的簡單程序。
// CPP program to illustrate
// concept of Virtual Functions
#include <iostream>
using namespace std;
class base {
public:
virtual void print()
{
cout << "print base class" << endl;
}
void show()
{
cout << "show base class" << endl;
}
};
class derived:public base {
public:
void print()
{
cout << "print derived class" << endl;
}
void show()
{
cout << "show derived class" << endl;
}
};
int main()
{
base* bptr;
derived d;
bptr = &d;
// virtual function, binded at runtime
bptr->print();
// Non-virtual function, binded at compile time
bptr->show();
}
輸出:
print derived class show base class
說明:運行時多態隻能通過基類類型的指針(或引用)來實現。同樣,基類指針可以指向基類的對象以及派生類的對象。在上麵的代碼中,基類指針“ bptr”包含派生類的對象“ d”的地址。
延遲綁定(運行時)是根據指針的內容(即指針指向的位置)完成的,而早期綁定(編譯時)是根據指針的類型完成的,因為print()函數是用virtual關鍵字聲明的,因此它將在運行時綁定(輸出是打印派生類,因為指針指向派生類的對象),並且show()是非虛擬的,因此它將在編譯時綁定(輸出是顯示基類,因為指針是基類型的)。
注意:如果我們在基類中創建了一個虛函數,並且在派生類中對其進行了覆蓋,那麽在派生類中就不需要virtual關鍵字,則函數會自動被視為派生類中的虛函數。
Working of virtual functions(concept of VTABLE and VPTR)
如此處所討論的,如果一個類包含一個虛函數,那麽編譯器本身會做兩件事:
- 如果創建了該類的對象,則將虛擬指針(VPTR)作為該類的數據成員插入,以指向該類的VTABLE。對於創建的每個新對象,將插入一個新的虛擬指針作為該類的數據成員。
- 不管是否創建對象,函數指針的靜態數組都稱為VTABLE,其中每個單元格包含該類中包含的每個虛函數的地址。
考慮下麵的示例:
// CPP program to illustrate
// working of Virtual Functions
#include <iostream>
using namespace std;
class base {
public:
void fun_1() { cout << "base-1\n"; }
virtual void fun_2() { cout << "base-2\n"; }
virtual void fun_3() { cout << "base-3\n"; }
virtual void fun_4() { cout << "base-4\n"; }
};
class derived:public base {
public:
void fun_1() { cout << "derived-1\n"; }
void fun_2() { cout << "derived-2\n"; }
void fun_4(int x) { cout << "derived-4\n"; }
};
int main()
{
base* p;
derived obj1;
p = &obj1;
// Early binding because fun1() is non-virtual
// in base
p->fun_1();
// Late binding (RTP)
p->fun_2();
// Late binding (RTP)
p->fun_3();
// Late binding (RTP)
p->fun_4();
// Early binding but this function call is
// illegal(produces error) becasue pointer
// is of base type and function is of
// derived class
// p->fun_4(5);
}
輸出:
base-1 derived-2 base-3 base-4
說明:最初,我們創建一個基類類型的指針,並使用派生類對象的地址對其進行初始化。當我們創建派生類的對象時,編譯器將創建一個指針,作為包含該派生類的VTABLE地址的類的數據成員。
上例中使用了後期綁定和早期綁定的類似概念。對於fun_1()函數調用,該函數的基類版本被調用,fun_2()在派生類中被覆蓋,因此派生類版本被調用,fun_3()在派生類中不被覆蓋,它是虛函數,因此該基類版本被調用,同樣,fun_4()不會被覆蓋,因此將調用基類版本。
注意:派生類中的fun_4(int)與基類中的虛函數fun_4()不同,因為這兩個函數的原型都不同。
注:本文由純淨天空篩選整理自 Virtual Function in C++。非經特殊聲明,原始代碼版權歸原作者所有,本譯文未經允許或授權,請勿轉載或複製。