多態(上)
多態上文章目錄多態上1. 多態的概念2. 多態的定義及實現2.1 多態的構成條件2.1.1 實現多態還有兩個必須重要條件結合動物例子串起來看2.1.2 虛函數2.1.3 虛函數的重寫/覆蓋坑點總結2.1.4 多態場景的?個選擇題2.1.5 虛函數重寫的?些其他問題1.協變Covariance1.1 結合協變代碼講解:1.2 協變完整帶注釋代碼2. 析構函數的虛函數重寫面試重點最重要實際意義面試核心1. 析構代碼逐行講解反面場景坑2. 虛析構完整帶注釋代碼3. 考試 / 面試小結2.1.6 override 和final關鍵字1、override檢查是否成功重寫虛函數問題背景override 的作用2. final兩個用途用途 1修飾虛函數 —— 禁止派生類重寫這個虛函數用途 2修飾類 —— 禁止這個類被繼承總結補充小對比2.1.7 重載/重寫/隱藏的對比1. 重載同一作用域2. 重寫 / 覆蓋實現多態繼承體系 virtual 虛函數3. 隱藏又叫遮蔽繼承體系不滿足重寫條件總結對照表方便記憶1. 多態的概念同一個動作交給不同對象去做表現出不一樣的效果這就是多態多種形態多態分成兩大類靜態多態編譯時、動態多態運行時靜態多態【編譯階段就確定好調用哪個函數】代表函數重載、模板同一個函數名字根據你傳入不同參數直接確定執行哪段代碼程序還沒跑起來編譯的時候編譯器一看參數就知道要用哪個方法。舉例子add(1,2)兩個數字相加add(a,b)兩個字符串拼接函數名都是 add參數不一樣功能不一樣。代碼編譯的時候就分清調用哪個所以叫靜態 / 編譯時多態。動態多態【程序跑起來之后才確定執行什么代碼】面向對象重點規則同一個行為傳入不同對象產生不同結果程序運行的時候才決定用哪段邏輯原文兩個例子通俗翻譯例子 1買票動作【買票】普通人對象執行買票 → 付全款學生對象執行買票 → 半價優惠軍人對象執行買票 → 優先購票動作名稱不變都是 “買票”對象不一樣行為結果不一樣。例子 2動物叫喚統一動作【叫】貓對象調用【叫】 → 喵喵喵狗對象調用【叫】 → 汪汪汪靜態多態 vs 動態多態最簡單區分? 靜態多態看參數區分邏輯編譯時定好? 動態多態看對象區分邏輯程序運行時才確定2. 多態的定義及實現2.1 多態的構成條件多態是?個繼承關系的下的類對象去調?同?函數產?了不同的?為。?如Student繼承了 Person。Person對象買票全價Student對象優惠買票。2.1.1 實現多態還有兩個必須重要條件條件 1要用基類指針 / 基類引用去調用函數基類相當于通用模板比如人、動物派生類是具體種類學生、貓、軍人、狗。普通指針只能固定指向一種對象基類指針 / 引用很靈活既能指向父類對象也能指向子類對象。舉例子基類 動物派生類 貓、狗動物* p; // 基類指針 p new 貓(); // 可以指向貓 p new 狗(); // 也可以指向狗如果不用基類指針直接用貓指針只能指向貓狗指針只能指向狗沒法統一用一套代碼接收多種子類自然談不上多態。簡單理解基類指針就是一個 “通用容器”能裝下所有子類。條件 2調用的函數必須是虛函數并且子類完成重寫覆蓋基類里的函數前面加上virtual它就變成虛函數。派生類寫一個一模一樣的函數函數名、參數、返回值完全匹配這就叫重寫 / 覆蓋。沒有虛函數會發生什么就算基類指針指向貓、指向狗調用方法時只會死板執行基類里面的代碼不會自動調用貓、狗自己實現的版本出不來多態效果虛函數作用相當于給程序開了一個開關。程序運行的時候不去死板看指針是什么類型而是看指針真正指向的是什么對象調用這個對象自己重寫的函數。結合動物例子串起來看基類【動物】里面寫虛函數virtual 叫()子類【貓】重寫叫()喵喵喵子類【狗】重寫叫()汪汪汪動物* a new 貓(); a-叫(); // 輸出喵喵喵 a new 狗(); a-叫(); // 輸出汪汪汪滿足兩條規則多態生效以下是反面教材如果函數不加 virtual不是虛函數基類指針哪怕指向貓調用叫()只會執行動物默認的叫聲不會調用貓的版本。如果不用基類指針直接用貓指針、狗指針各自調用雖然也能正常叫喚但是只是普通函數調用不是多態沒法用統一的指針接收所有子類。2.1.2 虛函數類成員函數前?加virtual修飾那么這個成員函數被稱為虛函數。注意?成員函數不能加virtual修飾。class Person { public: virtual void BuyTicket() { cout 買票全價 endl;} };2.1.3 虛函數的重寫/覆蓋(1)定義基類寫了一個虛函數子類里面寫一個函數函數名一模一樣、參數一模一樣、返回值符合要求。這就叫子類重寫覆蓋了基類虛函數。(2) 重點注意點子類可以不寫virtual依然能構成重寫基類函數已經是虛函數繼承到子類后這個函數天生自帶虛函數身份。哪怕子類函數前面省略virtual依舊算是重寫多態照樣生效。?但是不推薦省略 virtual代碼可讀性很差規范開發都建議寫上。考試高頻坑選擇題經常故意把子類的virtual刪掉迷惑你很多人誤以為沒有 virtual 就不能重寫、無法多態這是易錯點。簡單記住虛屬性是從基類繼承下來的子類可省略 virtual但規范上一定要加上。(3) 補充一個硬性前提想要實現動態多態必須滿足重寫如果函數名字 / 參數不一樣那不叫重寫自然觸發不了多態。4結合兩段代碼講解案例 1買票 Person Studentclass Person { public: virtual void BuyTicket() { cout 買票-全價 endl; } }; class Student : public Person { public: virtual void BuyTicket() { cout 買票-打折 endl; } };基類PersonBuyTicket()帶有virtual是虛函數子類Student函數名BuyTicket、參數列表、返回值和基類完全一致 →構成重寫void Func(Person* ptr) // 條件1基類指針 { ptr-BuyTicket(); } int main() { Person ps; Student st; Func(ps); // 普通人類對象 → 全價買票 Func(st); // 學生對象 → 打折買票 return 0; }? 兩條動態多態條件全部滿足① 使用基類指針調用函數② 虛函數 子類完成重寫運行結果買票-全價 買票-打折 關鍵點指針永遠都是Person*類型但是程序運行時看指針指向什么對象就調用哪個類的函數這就是多態。拓展測試把 Student 里面的 virtual 刪掉void BuyTicket() { cout 買票-打折 endl; }依然構成重寫多態依舊生效只是代碼不規范考試要認清這個情況。案例 2動物叫聲 Animal Dog Catclass Animal { public: virtual void talk() const {} }; class Dog : public Animal { public: virtual void talk() const { std::cout 汪汪 std::endl; } }; class Cat : public Animal { public: virtual void talk() const { std::cout (^ω^)喵 std::endl; } };基類Animal擁有虛函數talk()Dog、Cat 中talk()函數名、參數、const 修飾、返回值完全匹配基類構成重寫。void letsHear(const Animal animal) // 條件1基類引用指針/引用二選一都可以 { animal.talk(); } int main() { Cat cat; Dog dog; letsHear(cat); letsHear(dog); return 0; }? 使用基類引用也是實現多態合法方式 虛函數重寫滿足條件。運行結果(^ω^)喵 汪汪再次區分指針、引用都能實現多態直接用父類對象接收子類值傳遞切割無法實現多態坑點總結子類重寫虛函數時省略 virtual依舊是重寫能夠觸發多態很多考生誤以為子類必須寫 virtual 才算重寫這是出題人最愛設置的陷阱但是工程開發規范建議子類重寫虛函數時主動寫上 virtualC11 推薦再加 override 關鍵字如果函數名 / 參數不一樣不叫重寫叫函數隱藏無法實現多態2.1.4 多態場景的?個選擇題各位大佬請亮出您的答案答案B: B-1解釋先看能不能觸發多態基類 Avirtual void func(int val 1)派生類 Bvoid func(int val 0)函數名、返回值、參數列表完全相同構成虛函數重寫子類不寫 virtual 依然有效。調用流程梳理B* p new B; p-test();test()這個函數定義在 A 類里面virtual void test(){ func(); }雖然 test 寫在 A 中但是 test 內部調用func()。由于 func 是虛函數會觸發多態執行 B 類重寫的 func。重點最終跑的是B::func()不是 A::func ()。最容易踩坑默認參數編譯期確定不參與多態? 記住鐵律虛函數是運行時確定調用哪個函數函數默認參數值是編譯階段就確定好的不會跟著子類改變test()屬于 A 類的成員函數編譯的時候編譯器看到func();無傳參直接拿A 類 func 的默認參數 val1。運行時執行的是 B 的 func 函數但傳入的參數值是編譯期敲定的1。最終執行運行B::func(1)打印輸出B-1總結調用哪個函數A::func 還是 B::func運行時看虛函數參數默認值在編譯時就定死由調用處所在的類版本決定不會動態跟隨子類易錯辨析很多人會誤以為調用 B 的 func就應該使用 B 的默認參數 0。?錯誤默認參數不參與動態多態編譯時在 A 的 test 函數內部func 不傳參直接綁定 A 的默認值 1。拓展驗證如果代碼寫成A* p new B(); p-func(); // 依然輸出 B-1同樣原理對應選項: 選項B: B-1?簡易流程圖p-test()→ 執行 A::test () → test 里面調用 func ()→ 虛函數多態運行找到 B::func→ 參數編譯階段確定為 A 的默認值1→ 執行B::func(1)→ 輸出B-12.1.5 虛函數重寫的?些其他問題1.協變Covariance正常虛函數重寫要求返回值必須一模一樣。協變是唯一的特例基類虛函數返回【基類指針 / 基類引用】派生類重寫的虛函數返回【派生類指針 / 派生類引用】這種返回值不一樣但依然合法構成重寫就叫做協變。?? 硬性限制只允許指針、引用普通對象不能協變必須是「基類 ← 派生類」的繼承關系日常開發極少使用了解概念即可。1.1 結合協變代碼講解:class A {}; class B : public A {}; class Person { public: virtual A* BuyTicket() { cout 買票-全價 endl; return nullptr; } }; class Student : public Person { public: virtual B* BuyTicket() { cout 買票-打折 endl; return nullptr; } };基類虛函數返回A*子類重寫函數返回B*B 公有繼承 A滿足協變條件 →合法重寫可以觸發多態void Func(Person* ptr) { ptr-BuyTicket(); } int main() { Person ps; Student st; Func(ps); // 輸出買票-全價 Func(st); // 輸出買票-打折 return 0; }運行邏輯Func 接收基類指針調用 BuyTicket。因為構成合法重寫多態生效。ptr 指向 Student 對象自動執行 Student::BuyTicket。1.2 協變完整帶注釋代碼#include iostream using namespace std; class A {}; // B公有繼承A滿足協變的繼承關系 class B : public A {}; class Person { public: // 基類虛函數返回基類A的指針 virtual A* BuyTicket() { cout 買票-全價 endl; return nullptr; } }; class Student : public Person { public: // 協變返回派生類B*允許構成重寫 virtual B* BuyTicket() { cout 買票-打折 endl; return nullptr; } }; // 參數基類指針滿足動態多態條件 void Func(Person* ptr) { ptr-BuyTicket(); } int main() { Person ps; Student st; Func(ps); Func(st); return 0; }2. 析構函數的虛函數重寫面試重點正常成員函數重寫函數名、參數必須完全相同。析構函數是編譯器特殊處理的例外源代碼里基類析構~A()派生類析構~B()名字看起來不同編譯階段編譯器把所有析構函數統一改名destructor只要基類析構加 virtual派生類析構自動構成重寫派生類析構寫不寫 virtual 都有效。最重要實際意義面試核心當用基類指針指向派生類對象執行delete 指針;如果基類析構不是虛函數只會調用基類析構派生類析構不執行。派生類里面申請的內存無法釋放 →內存泄漏。如果基類析構是虛函數觸發多態先調用派生類析構再調用基類析構資源完整釋放。1. 析構代碼逐行講解class A { public: // 基類析構設置為virtual virtual ~A() { cout ~A() endl; } }; class B : public A { public: // 自動重寫基類虛析構可省略virtual ~B() { cout ~B()-delete: _p endl; delete[] _p; // 釋放子類申請的堆內存 } protected: int* _p new int[10]; // 子類持有堆資源 };int main() { A* p1 new A; A* p2 new B; delete p1; // 釋放A對象調用 ~A() delete p2; // 基類析構是虛函數觸發多態 // 先調用 ~B() 釋放_p數組再調用 ~A() return 0; }? 運行輸出~A() ~B()-delete:0x... ~A()反面場景坑如果刪掉virtual變成~A(){}delete p2;只會執行~A()不會執行B ()_p指向的堆數組沒有釋放永久內存泄漏。面試標準答案如果一個類可能被繼承最好把基類析構定義為虛析構函數防止基類指針管理派生類對象時發生內存泄漏。2. 虛析構完整帶注釋代碼#include iostream using namespace std; class A { public: // 重點基類析構加上virtual成為虛析構函數 virtual ~A() { cout ~A() endl; } }; class B : public A { public: // 自動重寫基類虛析構virtual可寫可不寫推薦寫上增強可讀性 ~B() { cout ~B()-delete: _p endl; delete[] _p; // 釋放子類堆資源 } protected: // 子類在堆上開辟內存 int* _p new int[10]; }; int main() { A* p1 new A; A* p2 new B; delete p1; delete p2; return 0; }3. 考試 / 面試小結協變僅支持指針 / 引用日常開發很少用認識即可析構函數名稱源代碼不同編譯器統一改名滿足虛函數重寫條件被繼承的基類析構函數建議加 virtual避免基類指針 delete 派生類對象造成內存泄漏。2.1.6 override 和final關鍵字1、override檢查是否成功重寫虛函數問題背景虛函數重寫要求很嚴函數名、參數、返回值協變除外都要匹配。一不小心寫錯函數名、參數編譯器不會報錯它只會認為你在派生類新增了一個獨立函數沒有完成重寫。等到運行程序多態失效結果不對你還要花大量時間查 bug。override 的作用在派生類的虛函數后面寫上override編譯器會主動檢查這個函數能不能成功重寫基類的虛函數。滿足重寫規則正常編譯寫錯名字 / 參數無法構成重寫直接編譯報錯提前發現錯誤。示例class A { public: virtual void func(){} }; class B : public A { public: // 正確成功重寫 void func() override{} // void func(int x) override{} // 報錯參數不一樣無法重寫 };小建議寫重寫的虛函數時習慣性加上 override。2. final兩個用途用途 1修飾虛函數 —— 禁止派生類重寫這個虛函數基類虛函數后面加final后續所有子類都不能重寫它。子類強行重寫 → 編譯報錯。class A { public: virtual void func() final {} }; class B : public A { public: // void func(){} // 編譯報錯func被final禁止重寫 };用途 2修飾類 —— 禁止這個類被繼承類名后面加 final別的類不能繼承它class A final {}; // class B:public A{}; // 報錯A不能被繼承總結override告訴編譯器「我打算重寫虛函數幫我檢查有沒有寫錯」防止隱性 bug。final上鎖。修飾函數 不讓子類重寫修飾類 不讓別人繼承這個類。補充小對比override只用在派生類的虛函數final可以寫在基類虛函數后也可以寫在類名后2.1.7 重載/重寫/隱藏的對比如果追女生是每個少年男生心中第一件事喵那么搞清楚這三個對比就是現在的當務之急重中之重喵。其實我想說這是第0件事哈哈哈哈哈因為計算機的世界是二進制的花園喵先一句話分清場景重載同一個班級里重寫父子班級 虛函數隱藏父子班級、但是達不到重寫條件。1. 重載同一作用域條件同一個范圍函數名字一樣參數不一樣。例子班里同一個教室同一個類 / 同一個作用域有兩位老師都叫 “講課”講課 (講數學)講課 (講語文)名字都叫講課但是授課內容參數不同。調用的時候看你傳什么參數自動匹配對應函數。關鍵點全都在同一個教室同一作用域和繼承沒關系。返回值隨便可以相同也可以不同只看參數。// 同一個類內部屬于重載 class Person { public: void Talk(string subject); void Talk(int age); };2. 重寫 / 覆蓋實現多態繼承體系 virtual 虛函數條件父類、子類兩個不同作用域父類函數是 virtual 虛函數函數名、參數一致協變除外。例子父親開小店virtual 買票 () → 全價票兒子繼承店鋪重新改造規則買票 () → 學生打折票規則名稱一模一樣入參一樣。有人通過店鋪招牌父類指針買票時自動區分是誰經營店鋪執行對應的買票邏輯這就是多態。重點區別必須加virtual不加就不叫重寫。只有重寫才能觸發動態多態。class Person { public: virtual void BuyTicket() { cout 全價票; } }; class Student : public Person { public: void BuyTicket() override { cout 打折票; } // 重寫 };3. 隱藏又叫遮蔽繼承體系不滿足重寫條件規則父子類函數名相同只要構不成重寫全部是隱藏。兩種常見情況1父類沒有寫 virtual2有 virtual但是參數列表不一樣例子父親店鋪功能買票 ()無參數兒子店鋪買票 (int num)帶參數名字都叫買票但是參數不一樣。如果你拿著兒子店鋪的招牌調用買票 ()只會看到兒子自己的版本父親同名函數直接被藏起來了。想要調用父類版本必須手動指定Person::BuyTicket()。?? 重要坑隱藏≠重寫不會觸發多態額外補充父子類同名成員變量也屬于隱藏父親有個變量int id;子類也寫int id;子類 id 會隱藏父類 id。class Person { public: virtual void BuyTicket() { cout 全價票; } }; class Student : public Person { public: void BuyTicket(int x) { cout 打折票; } // 參數不一樣無法構成重寫屬于【隱藏】 };總結對照表方便記憶重載同一個房間同名不同參重寫父子房間、帶 virtual、簽名一致實現多態隱藏父子房間、名字相同但達不到重寫標準父類同名成員被遮蔽又是美好的一天各位觀眾姥爺們若有什么不懂的地方歡迎在評論區留言哦我會給出相應解釋的。如有什么疏忽的細節請點出我會及時糾正。感謝觀看。喵喵

相關新聞

新手避坑必看!2026尤克里里選購攻略,4款實測好琴不踩雷

新手避坑必看!2026尤克里里選購攻略,4款實測好琴不踩雷

新手選購尤克里里無需糾結復雜參數,把握核心選購邏輯就能輕松避坑。本文大幅精簡理論知識點,聚焦新手最關心的機型實測內容,精選四款經過市場驗證、口碑穩定的高性價比尤克里里,覆蓋入門試水、穩定練習、顏值剛需、長期進階全場景…

2026/7/30 19:43:26 閱讀更多
SpringBoot+Vue實現影院票務系統的高并發選座與實時通信

SpringBoot+Vue實現影院票務系統的高并發選座與實時通信

1. 項目概述:影院票務系統的全棧實現萬象影視購票平臺是一個典型的O2O電商系統,采用SpringBootVue的前后端分離架構。作為影院行業的數字化解決方案,它需要處理高并發選座鎖座、實時票價計算、優惠券核銷等核心業務場景。我在實際開發中發現&…

2026/8/1 10:26:51 閱讀更多
如何讓微信對話成為永恒的數字記憶?WeChatMsg完整指南

如何讓微信對話成為永恒的數字記憶?WeChatMsg完整指南

如何讓微信對話成為永恒的數字記憶?WeChatMsg完整指南 【免費下載鏈接】WeChatMsg 提取微信聊天記錄,將其導出成HTML、Word、CSV文檔永久保存,對聊天記錄進行分析生成年度聊天報告 項目地址: https://gitcode.com/GitHub_Trending/we/WeCha…

2026/8/1 20:42:51 閱讀更多
終極指南:使用FinBERT構建金融情感分析系統

終極指南:使用FinBERT構建金融情感分析系統

終極指南:使用FinBERT構建金融情感分析系統 【免費下載鏈接】finBERT Financial Sentiment Analysis with BERT 項目地址: https://gitcode.com/gh_mirrors/fi/finBERT 在當今金融市場中,準確解讀財經新聞、財報和社交媒體情緒對投資決策至關重要…

2026/8/1 20:42:50 閱讀更多
emlearn API完全參考:C與Python接口使用指南

emlearn API完全參考:C與Python接口使用指南

emlearn API完全參考:C與Python接口使用指南 【免費下載鏈接】emlearn Machine Learning inference engine for Microcontrollers and Embedded devices 項目地址: https://gitcode.com/gh_mirrors/em/emlearn emlearn是一款專為微控制器和嵌入式設備設計的機…

2026/8/1 20:32:50 閱讀更多
AMAT 0100-02186 I/O 分配 PCB

AMAT 0100-02186 I/O 分配 PCB

AMAT 0100-02186 I/O分配PCB板是應用材料(Applied Materials)公司生產的一款用于半導體設備的I/O信號分配電路板。該型號(0100-02186)的核心特點如下:專用于Endura等半導體工藝腔室。集成信號路由與分配功能。連接控制…

2026/8/1 0:09:33 閱讀更多
Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動機

Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動機

Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動機是日本日清(Nissei)品牌的一款工業用三相異步電機,適用于自動化設備及通用機械驅動。該型號(FFMN-32L-10-T0 40AX)的核心特點如下:三相交流異步電動機。額定…

2026/8/1 0:09:33 閱讀更多
AMAT 0100-02186 I/O 分配 PCB

AMAT 0100-02186 I/O 分配 PCB

AMAT 0100-02186 I/O分配PCB板是應用材料(Applied Materials)公司生產的一款用于半導體設備的I/O信號分配電路板。該型號(0100-02186)的核心特點如下:專用于Endura等半導體工藝腔室。集成信號路由與分配功能。連接控制…

2026/8/1 0:09:33 閱讀更多
Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動機

Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動機

Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動機是日本日清(Nissei)品牌的一款工業用三相異步電機,適用于自動化設備及通用機械驅動。該型號(FFMN-32L-10-T0 40AX)的核心特點如下:三相交流異步電動機。額定…

2026/8/1 0:09:33 閱讀更多