C++之多態(tài)(一個接口多種實現(xiàn)方式)
C++的多態(tài)(Polymorphism)是面向?qū)ο缶幊蹋∣OP)的三大核心特性之一(另外兩個是封裝和繼承),其核心思想是一個接口,多種實現(xiàn),即同一操作作用于不同對象時,可產(chǎn)生不同的執(zhí)行結(jié)果。
多態(tài)讓代碼更靈活、可擴展,是構(gòu)建復雜系統(tǒng)的重要工具。
一、多態(tài)的分類
C++的多態(tài)分為兩類:靜態(tài)多態(tài)(編譯時多態(tài))和動態(tài)多態(tài)(運行時多態(tài)),二者的核心區(qū)別在于“確定調(diào)用哪個函數(shù)的時機”——前者在編譯期確定,后者在運行期確定。

1. 靜態(tài)多態(tài)(編譯時多態(tài))
靜態(tài)多態(tài)是通過函數(shù)重載或運算符重載實現(xiàn)的,編譯器在編譯階段根據(jù)函數(shù)的參數(shù)列表(類型、數(shù)量、順序)或運算符的操作數(shù)類型,確定具體要調(diào)用的函數(shù)。
示例:函數(shù)重載實現(xiàn)靜態(tài)多態(tài)
#include <iostream>
using namespace std;
// 重載:同一作用域內(nèi),函數(shù)名相同,參數(shù)列表不同
int add(int a, int b) {
return a + b;
}
double add(double a, double b) { // 參數(shù)類型不同
return a + b;
}
int add(int a, int b, int c) { // 參數(shù)數(shù)量不同
return a + b + c;
}
int main() {
cout << add(1, 2) << endl; // 調(diào)用int add(int, int)
cout << add(1.5, 2.5) << endl; // 調(diào)用double add(double, double)
cout << add(1, 2, 3) << endl; // 調(diào)用int add(int, int, int)
return 0;
}
編譯器在編譯時會根據(jù)實參的類型和數(shù)量,自動匹配到對應的重載函數(shù),這就是靜態(tài)多態(tài)的體現(xiàn)。
2. 動態(tài)多態(tài)(運行時多態(tài))
動態(tài)多態(tài)是C++多態(tài)的核心,它通過繼承+虛函數(shù)實現(xiàn),函數(shù)的具體調(diào)用在程序運行時才確定,而非編譯時。這種機制讓基類的指針/引用可以靈活指向不同派生類對象,并調(diào)用對應派生類的實現(xiàn)。
核心條件:
- 必須存在繼承關(guān)系(基類與派生類);
- 基類中聲明虛函數(shù)(用
virtual關(guān)鍵字修飾); - 派生類重寫(override)基類的虛函數(shù)(函數(shù)名、參數(shù)列表、返回值必須完全一致,協(xié)變返回類型除外);
- 通過基類的指針或引用調(diào)用虛函數(shù)。
二、動態(tài)多態(tài)的實現(xiàn)原理
動態(tài)多態(tài)的核心是虛函數(shù)表(vtable) 和虛指針(vptr),這是編譯器在背后自動實現(xiàn)的機制。
1. 虛函數(shù)表(vtable)
- 當一個類中聲明了虛函數(shù)(或繼承了虛函數(shù)),編譯器會為該類生成一個虛函數(shù)表(本質(zhì)是一個函數(shù)指針數(shù)組),存儲該類所有虛函數(shù)的地址。
- 若派生類重寫了基類的虛函數(shù),派生類的虛函數(shù)表中會用自己的函數(shù)地址覆蓋基類對應虛函數(shù)的地址;未重寫的虛函數(shù),地址仍指向基類的實現(xiàn)。
2. 虛指針(vptr)
- 每個含有虛函數(shù)的類的對象,都會隱含一個虛指針(vptr),指向該類的虛函數(shù)表(vtable)。
- 當通過基類指針/引用調(diào)用虛函數(shù)時,程序會通過對象的vptr找到對應的vtable,再從vtable中取出函數(shù)地址并調(diào)用,這個過程在運行時完成(動態(tài)綁定)。
示例:動態(tài)多態(tài)的直觀體現(xiàn)
#include <iostream>
using namespace std;
// 基類:形狀
class Shape {
public:
// 虛函數(shù):繪制
virtual void draw() { // 用virtual聲明為虛函數(shù)
cout << "繪制基礎(chǔ)形狀" << endl;
}
// 虛析構(gòu)函數(shù)(避免內(nèi)存泄漏)
virtual ~Shape() {}
};
// 派生類:圓形(繼承Shape)
class Circle : public Shape {
public:
// 重寫基類的draw()
void draw() override { // override關(guān)鍵字顯式聲明重寫(C++11)
cout << "繪制圓形" << endl;
}
};
// 派生類:矩形(繼承Shape)
class Rectangle : public Shape {
public:
// 重寫基類的draw()
void draw() override {
cout << "繪制矩形" << endl;
}
};
// 統(tǒng)一接口:接收基類引用,調(diào)用draw()
void render(Shape& shape) {
shape.draw(); // 運行時根據(jù)實際對象類型,調(diào)用對應draw()
}
int main() {
Circle circle;
Rectangle rectangle;
render(circle); // 輸出:繪制圓形(實際是Circle對象)
render(rectangle); // 輸出:繪制矩形(實際是Rectangle對象)
// 基類指針指向派生類對象
Shape* shape1 = new Circle();
Shape* shape2 = new Rectangle();
shape1->draw(); // 輸出:繪制圓形
shape2->draw(); // 輸出:繪制矩形
delete shape1; // 虛析構(gòu)函數(shù)確保派生類析構(gòu)被調(diào)用
delete shape2;
return 0;
}
運行機制解析:
Shape類有虛函數(shù)draw(),編譯器為其生成vtable,存儲Shape::draw()的地址。Circle和Rectangle繼承Shape并重寫draw(),它們的vtable中,draw()的地址被替換為各自的實現(xiàn)(Circle::draw()和Rectangle::draw())。- 當
render函數(shù)接收Circle或Rectangle對象的引用時(本質(zhì)是基類引用指向派生類對象),調(diào)用draw()時會通過對象的vptr找到對應vtable,最終執(zhí)行派生類的實現(xiàn)——這就是運行時多態(tài)。
三、重寫(Override)的細節(jié)
派生類重寫基類虛函數(shù)時,必須滿足以下條件(否則可能變成“隱藏”而非“重寫”):
函數(shù)名、參數(shù)列表完全相同:參數(shù)的類型、數(shù)量、順序必須一致(若參數(shù)不同,會變成派生類的新函數(shù),隱藏基類函數(shù))。
返回值類型相同:除非是“協(xié)變返回類型”(即基類虛函數(shù)返回基類指針/引用,派生類重寫函數(shù)返回派生類指針/引用)。
class Base {};
class Derived : public Base {};
class A {
public:
virtual Base* func() { return new Base(); } // 基類返回Base*
};
class B : public A {
public:
Derived* func() override { return new Derived(); } // 派生類返回Derived*(協(xié)變)
};
基類函數(shù)必須是虛函數(shù):若基類函數(shù)未用virtual修飾,派生類即使同名同參,也只是“隱藏”基類函數(shù),而非重寫(無法觸發(fā)多態(tài))。
訪問權(quán)限不影響多態(tài):即使派生類重寫的函數(shù)是private,通過基類指針/引用調(diào)用時仍能正常觸發(fā)(因為訪問權(quán)限檢查在編譯期,多態(tài)調(diào)用在運行期)。
四、純虛函數(shù)與抽象類
為了強制派生類必須實現(xiàn)某些功能(如“所有形狀都必須能繪制”),C++引入純虛函數(shù)和抽象類:
- 純虛函數(shù):在虛函數(shù)聲明后加
=0,表示該函數(shù)沒有默認實現(xiàn),必須由派生類重寫。 - 抽象類:包含純虛函數(shù)的類(或繼承純虛函數(shù)且未重寫的類),不能實例化對象,只能作為基類被繼承。
示例:抽象類與純虛函數(shù)
class Shape {
public:
// 純虛函數(shù):強制派生類實現(xiàn)draw()
virtual void draw() = 0; // =0表示純虛函數(shù)
virtual ~Shape() {} // 抽象類也需要虛析構(gòu)
};
class Circle : public Shape {
public:
void draw() override { // 必須重寫,否則Circle也是抽象類
cout << "繪制圓形" << endl;
}
};
int main() {
// Shape s; // 錯誤:抽象類不能實例化
Shape* shape = new Circle(); // 正確:基類指針指向派生類對象
shape->draw(); // 輸出:繪制圓形
delete shape;
return 0;
}
抽象類的核心作用是定義“接口規(guī)范”,確保派生類遵循統(tǒng)一的行為契約(如Shape規(guī)定“必須能繪制”,所有派生類都必須實現(xiàn)draw())。
五、多態(tài)的應用與優(yōu)勢
- 提高代碼復用性:通過基類接口統(tǒng)一處理不同派生類對象(如
render函數(shù)無需為每個形狀單獨實現(xiàn))。 - 增強擴展性:新增派生類(如
Triangle)時,無需修改現(xiàn)有接口代碼(如render),只需實現(xiàn)draw()即可,符合“開閉原則”(對擴展開放,對修改關(guān)閉)。 - 模擬現(xiàn)實世界的多樣性:現(xiàn)實中同一行為(如“繪制”)作用于不同對象(圓、矩形)會有不同結(jié)果,多態(tài)完美映射這種關(guān)系。
六、注意事項
析構(gòu)函數(shù)建議聲明為虛函數(shù):當通過基類指針刪除派生類對象時,若基類析構(gòu)不是虛函數(shù),會只調(diào)用基類析構(gòu)而不調(diào)用派生類析構(gòu),導致內(nèi)存泄漏。
class Base {
public:
~Base() { cout << "Base析構(gòu)" << endl; } // 非虛析構(gòu)(危險)
};
class Derived : public Base {
public:
~Derived() { cout << "Derived析構(gòu)" << endl; }
};
int main() {
Base* p = new Derived();
delete p; // 僅輸出"Base析構(gòu)",Derived析構(gòu)未調(diào)用(內(nèi)存泄漏)
return 0;
}
解決:將基類析構(gòu)聲明為virtual ~Base() {},確保派生類析構(gòu)被調(diào)用。
避免在構(gòu)造/析構(gòu)函數(shù)中調(diào)用虛函數(shù):構(gòu)造派生類對象時,先調(diào)用基類構(gòu)造函數(shù),此時對象的動態(tài)類型仍為基類,調(diào)用虛函數(shù)會執(zhí)行基類版本;析構(gòu)時同理,可能導致不符合預期的結(jié)果。
虛函數(shù)表的開銷:每個含虛函數(shù)的類會增加vtable(靜態(tài)開銷),每個對象會增加vptr(動態(tài)內(nèi)存開銷,通常為4/8字節(jié)),但相比多態(tài)帶來的靈活性,這種開銷通??山邮?。
C++的多態(tài)通過“靜態(tài)多態(tài)(重載)”和“動態(tài)多態(tài)(虛函數(shù))”實現(xiàn),其中動態(tài)多態(tài)是核心,依賴虛函數(shù)表和虛指針實現(xiàn)運行時綁定。它讓代碼更靈活、可擴展,是構(gòu)建大型面向?qū)ο笙到y(tǒng)的基礎(chǔ)。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
C++類重載函數(shù)的function和bind使用示例
這篇文章主要介紹了C++類重載函數(shù)的function和bind使用示例,幫助大家更好的理解和使用c++,感興趣的朋友可以了解下2021-01-01

