C++---多態(一個接口多種實現)

C++的多態(Polymorphism)是面向對象編程(OOP)的三大核心特性之一(另外兩個是封裝和繼承),其核心思想是一個接口,多種實現,即同一操作作用于不同對象時,可產生不同的執行結果。多態讓代碼更靈活、可擴展,是構建復雜系統的重要工具。

一、多態的分類

C++的多態分為兩類:靜態多態(編譯時多態)和動態多態(運行時多態),二者的核心區別在于“確定調用哪個函數的時機”——前者在編譯期確定,后者在運行期確定。

在這里插入圖片描述

1. 靜態多態(編譯時多態)

靜態多態是通過函數重載運算符重載實現的,編譯器在編譯階段根據函數的參數列表(類型、數量、順序)或運算符的操作數類型,確定具體要調用的函數。

示例:函數重載實現靜態多態

#include <iostream>
using namespace std;// 重載:同一作用域內,函數名相同,參數列表不同
int add(int a, int b) {return a + b;
}double add(double a, double b) {  // 參數類型不同return a + b;
}int add(int a, int b, int c) {  // 參數數量不同return a + b + c;
}int main() {cout << add(1, 2) << endl;       // 調用int add(int, int)cout << add(1.5, 2.5) << endl;   // 調用double add(double, double)cout << add(1, 2, 3) << endl;    // 調用int add(int, int, int)return 0;
}

編譯器在編譯時會根據實參的類型和數量,自動匹配到對應的重載函數,這就是靜態多態的體現。

2. 動態多態(運行時多態)

動態多態是C++多態的核心,它通過繼承+虛函數實現,函數的具體調用在程序運行時才確定,而非編譯時。這種機制讓基類的指針/引用可以靈活指向不同派生類對象,并調用對應派生類的實現。

核心條件

  • 必須存在繼承關系(基類與派生類);
  • 基類中聲明虛函數(用virtual關鍵字修飾);
  • 派生類重寫(override)基類的虛函數(函數名、參數列表、返回值必須完全一致,協變返回類型除外);
  • 通過基類的指針或引用調用虛函數。

二、動態多態的實現原理

動態多態的核心是虛函數表(vtable)虛指針(vptr),這是編譯器在背后自動實現的機制

1. 虛函數表(vtable)
  • 當一個類中聲明了虛函數(或繼承了虛函數),編譯器會為該類生成一個虛函數表(本質是一個函數指針數組),存儲該類所有虛函數的地址。
  • 若派生類重寫了基類的虛函數,派生類的虛函數表中會用自己的函數地址覆蓋基類對應虛函數的地址;未重寫的虛函數,地址仍指向基類的實現。
2. 虛指針(vptr)
  • 每個含有虛函數的類的對象,都會隱含一個虛指針(vptr),指向該類的虛函數表(vtable)。
  • 當通過基類指針/引用調用虛函數時,程序會通過對象的vptr找到對應的vtable,再從vtable中取出函數地址并調用,這個過程在運行時完成(動態綁定)。

示例:動態多態的直觀體現

#include <iostream>
using namespace std;// 基類:形狀
class Shape {
public:// 虛函數:繪制virtual void draw() {  // 用virtual聲明為虛函數cout << "繪制基礎形狀" << endl;}// 虛析構函數(避免內存泄漏)virtual ~Shape() {}
};// 派生類:圓形(繼承Shape)
class Circle : public Shape {
public:// 重寫基類的draw()void draw() override {  // override關鍵字顯式聲明重寫(C++11)cout << "繪制圓形" << endl;}
};// 派生類:矩形(繼承Shape)
class Rectangle : public Shape {
public:// 重寫基類的draw()void draw() override {cout << "繪制矩形" << endl;}
};// 統一接口:接收基類引用,調用draw()
void render(Shape& shape) {shape.draw();  // 運行時根據實際對象類型,調用對應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;   // 虛析構函數確保派生類析構被調用delete shape2;return 0;
}

運行機制解析

  • Shape類有虛函數draw(),編譯器為其生成vtable,存儲Shape::draw()的地址。
  • CircleRectangle繼承Shape重寫draw(),它們的vtable中,draw()的地址被替換為各自的實現(Circle::draw()Rectangle::draw())。
  • render函數接收CircleRectangle對象的引用時(本質是基類引用指向派生類對象),調用draw()時會通過對象的vptr找到對應vtable,最終執行派生類的實現——這就是運行時多態。

三、重寫(Override)的細節

派生類重寫基類虛函數時,必須滿足以下條件(否則可能變成“隱藏”而非“重寫”):

  1. 函數名、參數列表完全相同:參數的類型、數量、順序必須一致(若參數不同,會變成派生類的新函數,隱藏基類函數)。
  2. 返回值類型相同:除非是“協變返回類型”(即基類虛函數返回基類指針/引用,派生類重寫函數返回派生類指針/引用)。
    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*(協變)
    };
    
  3. 基類函數必須是虛函數:若基類函數未用virtual修飾,派生類即使同名同參,也只是“隱藏”基類函數,而非重寫(無法觸發多態)。
  4. 訪問權限不影響多態:即使派生類重寫的函數是private,通過基類指針/引用調用時仍能正常觸發(因為訪問權限檢查在編譯期,多態調用在運行期)。

四、純虛函數與抽象類

為了強制派生類必須實現某些功能(如“所有形狀都必須能繪制”),C++引入純虛函數抽象類

  • 純虛函數:在虛函數聲明后加=0,表示該函數沒有默認實現,必須由派生類重寫。
  • 抽象類:包含純虛函數的類(或繼承純虛函數且未重寫的類),不能實例化對象,只能作為基類被繼承。

示例:抽象類與純虛函數

class Shape {
public:// 純虛函數:強制派生類實現draw()virtual void draw() = 0;  // =0表示純虛函數virtual ~Shape() {}  // 抽象類也需要虛析構
};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;
}

抽象類的核心作用是定義“接口規范”,確保派生類遵循統一的行為契約(如Shape規定“必須能繪制”,所有派生類都必須實現draw())。

五、多態的應用與優勢

  1. 提高代碼復用性:通過基類接口統一處理不同派生類對象(如render函數無需為每個形狀單獨實現)。
  2. 增強擴展性:新增派生類(如Triangle)時,無需修改現有接口代碼(如render),只需實現draw()即可,符合“開閉原則”(對擴展開放,對修改關閉)。
  3. 模擬現實世界的多樣性:現實中同一行為(如“繪制”)作用于不同對象(圓、矩形)會有不同結果,多態完美映射這種關系。

六、注意事項

  1. 析構函數建議聲明為虛函數:當通過基類指針刪除派生類對象時,若基類析構不是虛函數,會只調用基類析構而不調用派生類析構,導致內存泄漏。

    class Base {
    public:~Base() { cout << "Base析構" << endl; }  // 非虛析構(危險)
    };class Derived : public Base {
    public:~Derived() { cout << "Derived析構" << endl; }
    };int main() {Base* p = new Derived();delete p;  // 僅輸出"Base析構",Derived析構未調用(內存泄漏)return 0;
    }
    

    解決:將基類析構聲明為virtual ~Base() {},確保派生類析構被調用。

  2. 避免在構造/析構函數中調用虛函數:構造派生類對象時,先調用基類構造函數,此時對象的動態類型仍為基類,調用虛函數會執行基類版本;析構時同理,可能導致不符合預期的結果。

  3. 虛函數表的開銷:每個含虛函數的類會增加vtable(靜態開銷),每個對象會增加vptr(動態內存開銷,通常為4/8字節),但相比多態帶來的靈活性,這種開銷通常可接受。


C++的多態通過“靜態多態(重載)”和“動態多態(虛函數)”實現,其中動態多態是核心,依賴虛函數表和虛指針實現運行時綁定。它讓代碼更靈活、可擴展,是構建大型面向對象系統的基礎。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/919691.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/919691.shtml
英文地址,請注明出處:http://en.pswp.cn/news/919691.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【機器學習深度學習】vLLM的核心優化技術詳解

目錄 前言 一、vLLM簡介&#xff1a;為什么它如此重要&#xff1f; 二、核心技術一&#xff1a;PagedAttention — 顯存管理的革命 2.1 傳統注意力緩存的缺陷 2.2 分頁式存儲管理 三、核心技術二&#xff1a;張量并行 — 多GPU推理的基石 3.1 什么是張量并行&#xff1f…

MySQL 高級主題:索引優化、ORM 與數據庫遷移

第五部分&#xff1a;索引優化1. 為什么需要索引&#xff1f;索引是提高數據庫查詢性能的關鍵數據結構&#xff0c;它類似于書籍的目錄&#xff0c;可以幫助數據庫快速定位到所需數據&#xff0c;而不必掃描整個表。2. 索引類型主鍵索引 (PRIMARY KEY): 唯一且非空&#xff0c;…

Eplan教程:網絡與PLC

歡迎大家來到“Eplan帶你做項目”第六個過程。在第五個過程中&#xff0c;Eplan基于實際項目的繪制&#xff08;電氣設計中的電源回路以及電源分配相關回路&#xff09;重點分享分了“電機的供電和控制圖紙的繪制”。本文中&#xff0c;先猜個問題&#xff0c;設計一個PLC系統&…

大模型落地全攻略:從技術實現到場景應用

大語言模型&#xff08;LLM&#xff09;的快速發展正在重塑各行各業的智能化進程&#xff0c;但其落地應用仍面臨技術適配、場景融合、成本控制等多重挑戰。本文將系統解析大模型落地的四大核心方向 ——微調技術、提示詞工程、多模態應用和企業級解決方案&#xff0c;通過代碼…

【論文】Zotero文獻管理

Zotero文獻管理 寫論文前查找閱讀大量文獻&#xff0c;寫論文時引用文獻&#xff0c;都是一件非常麻煩的事情&#xff0c;一款合適的文獻管理工具可以幫助我們更快捷地完成這些任務。zotero作為一款免費開源的工具&#xff0c;可以實現文獻閱讀、同步管理以及引用管理。 安裝…

MsSQL 函數,實現數字轉換成人民幣大寫

MsSQL 函數&#xff0c;實現數字轉換成人民幣大寫-- 如果函數已存在則刪除 IF OBJECT_ID(dbo.ConvertToRMBChineseNew, FN) IS NOT NULLDROP FUNCTION dbo.ConvertToRMBChineseNew GOCREATE FUNCTION dbo.ConvertToRMBChineseNew (NumberInput SQL_VARIANT -- 使用 SQL_VARIANT…

OpenHarmony深度定制:從系統到模塊的全景剖析與自定義模塊實戰

摘要:OpenHarmony 作為面向萬物互聯時代的開源操作系統,其“系統-子系統-部件-模塊”的四層架構設計,為開發者提供了高度可裁剪、可擴展的能力。本文將系統梳理這四層結構的職責邊界與協作關系,并手把手演示如何向 OpenHarmony 新增一個可交付的自定義模塊(Module),幫助…

數字社會學是干什么的?數字社會學理論與數字社會學家唐興通講數字社會學書籍有哪些?AI社會學人工智能社會學理論框架

在當今社會&#xff0c;傳統物理空間和人際關系網絡成為了許多年輕人尋找合適伴侶的重大障礙。以深圳為例&#xff0c;這座移民城市的大部分居民都來自外地&#xff0c;年輕人的人脈關系、尤其是親戚關系大多仍在家鄉。這使得深圳的單身男女在交友和婚戀方面的選擇面變得狹窄&a…

數據庫-MYSQL配置下載

目錄 一.數據庫概念 一、數據庫的基本定義 二、數據庫管理系統&#xff08;DBMS&#xff09; 三、數據庫系統&#xff08;DBS&#xff09; 四、數據模型 五、數據庫的特點 六、數據庫的應用領域 二.MySql 一、開源免費&#xff0c;降低中大型項目成本 二、跨平臺與兼容…

Java 中表示數據集的常用集合類

Java 中表示數據集的常用集合類 Java 集合框架提供了多種數據結構來表示和操作數據集&#xff0c;每種集合類都有其特定的用途和性能特征。以下是主要的集合類及其特點&#xff1a; 一、List 接口及其實現類 1. ArrayList 特點&#xff1a;基于動態數組實現優點&#xff1a;隨機…

Django REST框架核心:GenericAPIView詳解

Django REST framework (DRF) 中 GenericAPIView 的源碼核心部分。 它是所有“泛型視圖”的基礎類&#xff0c;比如常用的 ListAPIView、RetrieveAPIView、CreateAPIView 都是繼承自它。&#x1f31f; 作用繼承自 APIView&#xff0c;因此仍然是一個標準的 DRF 視圖。提供了常用…

深入解析HashMap的存儲機制:擾動函數、哈希計算與索引定位

今天復習了一下HashMap的部分&#xff0c;寫一篇博客記錄一下今天學習內容雖然之前學習過&#xff0c;但由于后來沒怎么使用過而且也沒復習基本忘得差不多了在Java的HashMap中&#xff0c;高效存儲鍵值對的核心在于哈希算法和索引定位。本文將結合源碼逐步拆解存儲流程&#xf…

【機器學習 / 深度學習】基礎教程

階段一&#xff1a;機器學習 / 深度學習基礎教程定位&#xff1a;針對準備進入 AI多智能體開發 的初學者&#xff0c;打牢機器學習與深度學習的基礎。一、為什么需要學習機器學習/深度學習 在進入智能體&#xff08;Agent&#xff09;開發之前&#xff0c;必須具備一定的 機器學…

ESP32應用——HTTP client(ESP-IDF框架)

目錄 一、前言 二、URL 2.1 URL簡介 2.2 URL示例 三、HTTP 3.1 HTTP協議概述 3.2 HTTP的工作原理 3.2.1 HTTP 請求-響應流程 3.2.2 HTTP 請求結構 3.2.3 HTTP請求方法 3.2.4 HTTP響應結構 3.2.5 HTTP狀態碼 四、ESP HTTP 客戶端流程 五、ESP HTTP 客戶端實戰解析…

動學學深度學習07-現代卷積神經網絡

動學學深度學習pytorch 參考地址&#xff1a;https://zh.d2l.ai/ 文章目錄動學學深度學習pytorch1-第07章-現代卷積神經網絡1. AlexNet1.1 AlexNet 的核心貢獻是什么&#xff1f;1.2 AlexNet 與 LeNet 的主要區別有哪些&#xff1f;1.3 為什么 AlexNet 需要 GPU 訓練&#xff1…

詳細講解Java中的反射和經典面試題(保姆級別)

1.1 反射的概述&#xff1a;專業的解釋&#xff08;了解一下&#xff09;&#xff1a;是在運行狀態中&#xff0c;對于任意一個類&#xff0c;都能夠知道這個類的所有屬性和方法&#xff1b;對于任意一個對象&#xff0c;都能夠調用它的任意屬性和方法&#xff1b;這種動態獲取…

MyCAT完整實驗報告

MyCAT完整實驗報告 ? 前言 剛剛看了一下前面的那篇MyCAT的文章 感覺有一些問題 所以拿出一篇文章再說一下 單獨構建了完整的實驗環境 這樣會全面一點 ? 安裝MyCAT #跳過? 主從配置 #不多追溯 因為我們選擇的主從 也可以做雙主機 但我們后邊再說? 環境搭建 一、環境規劃 服務…

機器翻譯論文閱讀方法:頂會(ACL、EMNLP)論文解析技巧

更多內容請見: 機器翻譯修煉-專欄介紹和目錄 文章目錄 一、論文選擇:快速判斷論文價值 1.1 關注核心會議與子領域 1.2 篩選標準 1.3 預讀篩選 1.4 快速定位關鍵信息 二、精讀解析 2.1 問題定義(5分鐘) 2.2 方法解剖(15分鐘) 2.3 實驗深挖(20分鐘) 2.4 批判性思考(10分…

Transformer模型實戰篇

引入 基于Transformers的NLP解決方案的步驟如下&#xff1a;&#xff08;以文本分類為例&#xff09; 導入相關包&#xff0c;General&#xff0c;可以詢問ai需要導什么包加載數據集&#xff0c;Data_loader&#xff0c;Datasets數據集劃分&#xff0c;測試機&#xff0c;驗證集…

深入(流批【牛批】框架)Flink的機制

flink本身是專注有狀態的無限流處理&#xff0c;有限流處理【batch批次】是無限流處理的一中特殊情況&#xff01;應用場景實時ETL 集成流計算現有的諸多數據通道和SQL靈活的加工能力&#xff0c;對流式數據進行實時清洗、歸并和結構化 處理&#xff1b;同時&#xff0c;對離線…