首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C++ 虛函數深度解析:原理、實作與多態機制

     編輯:關於C++

虛函數是 C++ 實現執行期多態的核心機制。透過基類指標/參考指向派生類物件,呼叫虛函數時依物件實際型別動態綁定正確版本。

語法與規則

class Base {
public:
    virtual void Display() { cout << "Base"; } // 虛函數
    virtual ~Base() {} // 析構函數建議宣告為 virtual
};

class Derived : public Base {
public:
    void Display() override { cout << "Derived"; } // 覆寫
};
  • 僅非靜態成員函數可為 virtual
  • 建構函數不可為 virtual,析構函數應為 virtual
  • 派生類覆寫時自動成為虛函數,可加 override (C++11) 確保正確覆寫
  • 存取層級可為 private/protected/public

動態綁定三要件

  1. 函數宣告為 virtual
  2. 類別間存在公有繼承關係
  3. 透過基類指標/參考呼叫虛函數

範例

#include 
using namespace std;

class Shape {
public:
    virtual void Draw() { cout << "Shape\n"; }
    virtual ~Shape() {}
};

class Circle : public Shape {
public:
    void Draw() override { cout << "Circle\n"; }
};

class Rect : public Shape {
public:
    void Draw() override { cout << "Rectangle\n"; }
};

int main() {
    Shape* shapes[] = {new Shape, new Circle, new Rect};
    for (auto s : shapes) s->Draw();
    // 輸出: Shape, Circle, Rectangle
    for (auto s : shapes) delete s;
}

若移除 virtual,輸出將全為 Shape(靜態綁定)。

底層實作原理:虛函數表

編譯器為每個含虛函數的類別建立虛函數表,表中儲存虛函數位址。每個物件隱含一個 vptr 指向其類別的 vtable。

// 簡化模型
class A {
    virtual void Fun1() {}
    virtual void Fun2() {}
    // 物件記憶體佈局: [vptr] -> vtable_A [&A::Fun1, &A::Fun2]
};

class B : public A {
    void Fun1() override {}
    // 物件記憶體佈局: [vptr] -> vtable_B [&B::Fun1, &A::Fun2]
};

呼叫 ptr->Fun1() 時:

  1. 取得物件 vptr
  2. 查 vtable 第 0 項函數位址
  3. 跳轉執行

這解釋了虛函數呼叫的額外開銷(間接跳轉),但換來執行期多態彈性。

純虛函數與抽象類別

class Interface {
public:
    virtual void MustImplement() = 0; // 純虛函數
    virtual ~Interface() {}
};
  • 含純虛函數的類別為抽象類別,無法實例化
  • 派生類必須實作所有純虛函數,否則仍為抽象類別
  • 介面設計常用純虛函數定義契約

虛函數表指標驗證實驗

#include 
#include 
using namespace std;

class Base {
public:
    virtual void F1() { cout << "Base::F1\n"; }
    virtual void F2() { cout << "Base::F2\n"; }
};

class Derived : public Base {
public:
    void F1() override { cout << "Derived::F1\n"; }
    void F2() override { cout << "Derived::F2\n"; }
};

int main() {
    Derived d;
    Base* p = &d;

    // 手動模擬虛函數呼叫 (教學用,非標準做法)
    void** vptr = *reinterpret_cast(&p);
    typedef void(*FuncPtr)(Base*);
    FuncPtr f1 = reinterpret_cast(vptr[0]);
    FuncPtr f2 = reinterpret_cast(vptr[1]);
    f1(p); // Derived::F1
    f2(p); // Derived::F2
}

常見陷阱與最佳實踐

  • 析構函數必須虛擬化:否則 delete basePtr 僅呼叫基類析構,導致資源洩漏
  • 建構/析構函數中呼叫虛函數:行為特殊(呼叫當前類別版本),避免使用
  • 預設參數值:虛函數預設參數由靜態型別決定,建議避免在虛函數使用預設參數
  • override/final:C++11 關鍵字協助編譯器檢查覆寫正確性
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved