當前位置: 首頁>>代碼示例 >>用法及示例精選 >>正文


C++ Virtual用法及代碼示例


虛函數是在基類中聲明的成員函數,並由派生類重新定義(重寫)。當使用指針或對基類的引用來引用派生類對象時,可以為該對象調用虛擬函數並執行該派生類的函數版本。

  • 虛函數可確保為對象調用正確的函數,而不管用於函數調用的引用(或指針)的類型如何。
  • 它們主要用於實現運行時多態
  • 函數在基類中使用虛擬關鍵字聲明。
  • 函數調用的解析在運行時完成。

Rules for Virtual Functions

  1. 虛函數不能是靜態的,也不能是另一個類的朋友函數。
  2. 應該使用指針或基類類型的引用來訪問虛擬函數,以實現運行時多態。
  3. 虛擬函數的原型在基類和派生類中都應相同。
  4. 它們始終在基類中定義,而在派生類中被覆蓋。並非強製派生類重寫(或重新定義虛擬函數),在這種情況下,將使用函數的基類版本。
  5. 一個類可能具有虛擬析構函數,但不能具有虛擬構造函數。

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)

如此處所討論的,如果一個類包含一個虛函數,那麽編譯器本身會做兩件事:

  1. 如果創建了該類的對象,則將虛擬指針(VPTR)作為該類的數據成員插入,以指向該類的VTABLE。對於創建的每個新對象,將插入一個新的虛擬指針作為該類的數據成員。
  2. 不管是否創建對象,函數指針的靜態數組都稱為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++。非經特殊聲明,原始代碼版權歸原作者所有,本譯文未經允許或授權,請勿轉載或複製。