上一章節:
七、重學C++—靜態多態(編譯期)-CSDN博客https://blog.csdn.net/weixin_36323170/article/details/146999362?spm=1001.2014.3001.5502
本章節代碼:
cpp/dynamicPolymorphic.cpp · CuiQingCheng/cppstudy - 碼云 - 開源中國
https://gitee.com/cuiqingcheng/cppstudy/blob/master/cpp/dynamicPolymorphic.cpp

目錄
上一章節:
本章節代碼:
一、引言
二、概念:什么是運行時多態?
三、實現機制:虛函數
虛函數表和虛函數表指針的作用
四、純虛函數,抽象類/接口類
抽象類
接口類
五、多態的優點和應用場景
優點
應用場景
六、總結
下一章節:
一、引言
????????在 C++ 的奇妙世界里,多態是一個強大而迷人的特性,它就像代碼世界中的變形金剛,讓程序能夠根據不同的情況展現出多樣的行為。而 運行期多態,作為多態的重要組成部分 ,更是為程序帶來了無與倫比的靈活性和擴展性。今天,我們就一起來深入探秘 C++ 運行期多態的奧秘。
二、概念:什么是運行時多態?
????????運行期多態,也被稱為 動態多態 ,是指在 程序運行時才確定具體要調用哪個函數 。它通過 虛函數和繼承機制來實現,允許我們使用基類的指針或引用調用派生類的函數,從而實現不同對象的不同行為 。簡單來說,就是在運行時 根據實際對象的類型來決定執行哪個函數 ,就像變形金剛在戰斗中根據不同的場景變換形態一樣。
三、實現機制:虛函數
????????虛函數是實現運行期多態的關鍵。在基類中,我們使用?“ virtual” ?關鍵字來聲明虛函數, 派生類可以重寫這些虛函數以實現自己的特定行為 。當通過基類的指針或引用調用虛函數時,程序會在運行時根據指針或引用實際指向的對象類型來決定調用哪個版本的函數。
class base{public:virtual void func() // 虛函數{ }
}class A:public base{public:void func() override {// 重寫實現虛函數 }
}
每一個 包含虛函數的類中均有一個虛函數表指針,指向虛函數表;如下圖:

虛函數表和虛函數表指針的作用
虛函數表( VTable): 每個 包含虛函數的類都有一個虛函數表,這是一個存儲虛函數地址的數組 。表中的每個條目都是一個指向虛函數的指針,這些虛函數按照它們在類中聲明的順序排列。
虛函數表指針( VPTR): 每個包含虛函數的類的對象都有一個虛函數表指針,它指向該類對應的虛函數表 。借助這個指針,對象能夠在運行時找到正確的虛函數來調用。
實例:一個動物的基類,均有叫這個實現函數,對于兩個繼承自動物類的狗類/貓類,分別實現其叫這個函數,代碼如下:
/**** C++ 多態* 動態多態* 虛函數/純虛函數* 抽象類/接口類*/#include <iostream>// 基類 Animal
class Animal {
public:// 虛函數 speak()virtual void speak() {std::cout << "動物發出聲音" << std::endl;}
};// 派生類 Cat
class Cat : public Animal {
public:// 重寫 speak() 函數void speak() override {std::cout << "喵~" << std::endl;}
};// 派生類 Dog
class Dog : public Animal {
public:// 重寫 speak() 函數void speak() override {std::cout << "汪!" << std::endl;}
};int main() {// 創建 Cat 和 Dog 對象Cat cat;Dog dog;// 使用基類指針指向派生類對象Animal* animal1 = &cat;Animal* animal2 = &dog;// 調用虛函數animal1->speak(); // 輸出:喵~animal2->speak(); // 輸出:汪!animal1->Animal::speak(); // 輸出:動物發出聲音return 0;
}
這里 基類的指針,會根據實際指向的對象類型,從而決定默認調用的是重寫的虛函數 ,若還想調用基類原來的虛函數,需要指定作為基類方可調用;
四、純虛函數,抽象類/接口類
上面定義的基類中,虛函數還是實現了,若 一個虛函數沒有實現的代碼段體,則是純虛函數,定義如下:
virtual int func() = 0;
抽象類
????????包含純虛函數的類被稱為抽象類 , 抽象類不能被實例化 ,只能作為基類被派生類繼承。 派生類必須重寫抽象類中的純虛函數,否則派生類也會成為抽象類。只有實現了純虛函數的派生類,才能實例化對象。
接口類
特殊的抽象類, 類中所有函數都是純虛函數
實例如下:
/**** C++ 多態* 動態多態* 虛函數/純虛函數* 抽象類/接口類*/#include <iostream>// 抽象類class Organism{
public:virtual void growup() = 0;virtual void eating(){std::cout << "eating everything" << std::endl;};};// 基類 Animal 還是抽象類,無法實例化對象
class Animal : public Organism{
public:// 虛函數 speak()virtual void speak() {std::cout << "動物發出聲音" << std::endl;}
};// 派生類 Cat
class Cat : public Animal {
public:void growup(){std::cout<< "貓越長越大" << std::endl;}// 重寫 speak() 函數void speak() override {std::cout << "喵~" << std::endl;}
};// 派生類 Dog
class Dog : public Animal {
public:void growup(){std::cout<< "狗越長越大" << std::endl;}// 重寫 speak() 函數void speak() override {std::cout << "汪!" << std::endl;}
};int main() {// 創建 Cat 和 Dog 對象Cat cat;Dog dog;// 使用基類指針指向派生類對象Animal* animal1 = &cat;Animal* animal2 = &dog;// 調用虛函數animal1->speak(); // 輸出:喵~animal2->speak(); // 輸出:汪!animal1->growup(); // 輸出:喵~animal2->growup(); // 輸出:汪!animal1->Animal::speak(); // 輸出:動物發出聲音return 0;
}
五、多態的優點和應用場景
優點
- 靈活性和擴展性:運行期多態允許我們在不修改現有代碼的情況下,輕松地添加新的派生類和功能。
- 代碼復用:通過使用基類的指針或引用,我們可以編寫通用的代碼來處理不同類型的對象,從而提高代碼的復用性。例如,圖形繪制程序中,我們可以編寫一個通用的函數來計算不同圖形的面積,而不需要為每種圖形編寫單獨的函數。
- 可維護性:運行期多態使得代碼的結構更加清晰,易于理解和維護。通過將不同的行為封裝在不同的派生類中,我們可以避免代碼的冗余和混亂。
應用場景
- 游戲開發:在游戲開發中,運行期多態可以用于實現不同角色的不同行為,如攻擊、防御、移動等。例如,不同類型的角色(如戰士、法師、刺客)可以繼承自一個基類?Character,并分別重寫?attack()、defend()、move()?等函數,以實現不同的行為。
- 圖形處理:在圖形處理中,運行期多態可以用于實現不同圖形的不同繪制方法。例如,不同類型的圖形(如圓形、矩形、三角形)可以繼承自一個基類?Shape,并分別重寫?draw()?函數,以實現不同的繪制方法。
- 數據庫訪問:在數據庫訪問中,運行期多態可以用于實現不同數據庫的不同訪問方法。例如,不同類型的數據庫(如 MySQL、Oracle、SQLite)可以繼承自一個基類?Database,并分別重寫?connect()、query()、insert()?等函數,以實現不同的訪問方法。
六、總結
????????運行期多態是 C++ 中一個非常強大和重要的特性,它通過虛函數和繼承機制實現了在程序運行時根據實際對象的類型來決定執行哪個函數的功能。
下一章節:
九、重學C++—類和函數-CSDN博客
https://blog.csdn.net/weixin_36323170/article/details/147017358?spm=1001.2014.3001.5501
