【C++】“多態”特性

在這里插入圖片描述

文章目錄

  • 一、多態的概念
  • 二、多態的定義實現
    • 1. 多態的構成條件
      • 1.1 虛函數
      • 1.2 虛函數的重寫
    • 2. 多態的調用
    • 3. 虛函數重寫的其他問題
      • 3.1 協變
      • 3.2 析構函數的重寫
  • 三、override和final關鍵字
  • 四、重載/重寫/隱藏的對比
  • 五、純虛函數和抽象類
  • 六、多態的原理

C++的三大主要特性:封裝(類和對象)、繼承、多態。前兩者我們已經學習過了,今天最后來認識一下“多態”特性。

一、多態的概念

多態,就是“多種形態”,指函數的行為可以有多種形態。多態一般分為編譯時多態(靜態多態)和運行時多態(動態多態)。其中,編譯時多態主要就是之前講的函數重載和函數模板,它們傳不同類型的參數就可以調用不同的函數,通過參數的不同達到不同的形態效果,之所以叫編譯時多態,是因為傳參匹配這一過程是在編譯時期完成的。
運行時多態,具體指一個函數傳不同的對象就會完成不同的行為,達到多種形態。比如寫一個買火車票行為,傳普通人類對象是“全價”,傳學生類對象是“打折”,傳軍人類對象就是“優先”,一個函數(行為),根據傳的對象類型不同,就有不同作用。今天談的“多態”指的主要就是運行時多態。

二、多態的定義實現

1. 多態的構成條件

(運行時)多態是一個繼承體系下的類對象,去調用同一函數,而產生不同的行為。比如,Person類為基類,Student類繼承了Person類,Soldier類繼承了Person類,那么這三類的對象買票行為就有不同的效果。

實現多態還有兩個重要的條件:

  • 必須是基類的指針類型或引用類型去調用虛函數。
  • 被調用的函數必須是虛函數,并且完成了虛函數的重寫(覆蓋)。

我們依次來說明:
要實現多態效果,首先必須是基類的指針或引用去調用,因為只有這樣才既能指向基類對象又能指向派生類對象(的基類的切片)。
第二,派生類必須對基類的虛函數完成重寫,重寫了,基類和派生類才能有這個函數的不同實現方法,多態的不同形態效果才能達到。

1.1 虛函數

類的成員函數前加關鍵字virtual修飾,那么這個成員函數被稱為虛函數,非成員函數不能加virtual修飾。如:

class Person
{
public:virtual void BuyTicket(){cout << "全價" << endl;}
};

虛函數的存在就是為了給多態服務的。

1.2 虛函數的重寫

虛函數的重寫指:派生類中有一個跟基類虛函數“三同”的虛函數,則稱派生類的這個虛函數完成了對基類虛函數的重寫。“三同”指的是,函數名相同、返回類型相同、參數類型。
在派生類中重寫基類虛函數時,派生類的虛函數前可以不加virtual修飾,也可以構成重寫,因為繼承后基類的虛函數在派生類中仍保持虛函數屬性。但是這種寫法不規范,實際使用還是建議派生類的虛函數前寫上virtual(但基類的虛函數前是必須寫virtual的)。不過在考試中也有可能故意埋這個坑,注意判斷。
舉例:

class Person
{
public:virtual void BuyTicket(){cout << "全價" << endl;}
};class Student : public Person
{
public:// 注意保證“三同”virtual void BuyTicket(){cout << "打折" << endl;}
};

2. 多態的調用

利用多態的特性,我們可以模擬出簡單的買火車票行為:傳普通人類對象是“全價”,傳學生類對象是“打折”,傳軍人類對象就是“優先”:

class Person
{
public:virtual void BuyTicket(){cout << "全價" << endl;}
};class Student : public Person
{
public:virtual void BuyTicket(){cout << "打折" << endl;}
};class Soldier : public Person
{
public:virtual void BuyTicket(){cout << "優先" << endl;}
};

但是記得剛才說的多態的第一個條件,必須是基類的指針類型或引用類型去調用虛函數,比如:
在這里插入圖片描述

3. 虛函數重寫的其他問題

3.1 協變

派生類重寫基類虛函數時,重寫時虛函數返回類型也可以不一樣。基類虛函數可以返回自己或其他基類對象的指針或引用,派生類虛函數可以返回自己或其他派生類對象的指針或引用。這種方式稱為卸變,但是它的實際意義并不大,簡單了解即可。

class Person
{
public:virtual Person* BuyTicket(){cout << "全價" << endl;return nullptr;}
};class Student : public Person
{
public:virtual Student* BuyTicket(){cout << "打折" << endl;return nullptr;}
};

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;}
};

3.2 析構函數的重寫

析構函數十分特殊,編譯器會對析構函數的名字進行特殊處理——編譯后析構函數的名稱會統一處理成destructor,再加上析構函數都是沒有返回、沒有參數的。因此,基類和派生類的析構函數總會構成“三同”,如果不加virtual修飾成虛函數,那么基類和析構函數既然是“同名”的,就構成隱藏關系了所以,基類的析構函數一定要加virtual修飾成虛函數,派生類析構函數的virtual可寫可不寫

比如:

class A
{
public:~A(){cout << "~A()" << endl;}
};class B : public A
{
public:~B(){cout << "~B()" << endl;delete p;}
protected:int* p = new int[10];
};

在這里插入圖片描述
可見,如果~A()不加virtual,那么delete p2時只會調用A的析構函數,沒有調用B的析構函數,導致了內存泄漏問題。

這樣就沒問題了。
在這里插入圖片描述

總而言之,基類的析構函數建議設計為虛函數

三、override和final關鍵字

C++提供了兩個新的關鍵字:

  • override:
    C++對虛函數重寫的要求是很嚴格的,但是有時候我們可能粗心沒有滿足多態的要求,但是語法沒問題編譯也不會有問題,只有在運行結果不是預期情況下我們才能發現問題。因此C++11提供了override關鍵字,寫在派生類的想要重寫的虛函數后,幫助用戶檢測是否完成了重寫
    在這里插入圖片描述
  • final
    之前繼承提到過,如果一個類不想被繼承,就在類名后加final修飾。除此之外final還有一個功能,如果我們不想讓基類的虛函數被重寫,就在這個虛函數()后加final修飾在這里插入圖片描述

四、重載/重寫/隱藏的對比

這三個概念比較相近,要注意區別:

函數重載:

  • 兩個函數在同一作用域
  • 函數名相同,參數的類型或個數不同,返回值可同可不同

虛函數重寫:

  • 兩個函數分別在一個繼承體系的基類和派生類中
  • 函數名、參數、返回值相同,協變除外
  • 兩個函數必須都是虛函數

隱藏:

  • 兩個函數分別在一個繼承體系的基類和派生類中
  • 函數名相同
  • 兩個函數只要不構成重寫,就是隱藏關系
  • 基類和派生類的成員變量相同也構成隱藏關系

五、純虛函數和抽象類

如果在一個虛函數的()后寫上= 0,則這個虛函數稱為純虛函數,純虛函數不需要定義實現(可以實現但是沒有意義),只要聲明即可。
包含純虛函數的類稱為抽象類,抽象類不能實例化出對象,如果一個抽象類的派生類繼承后不重寫純虛函數,則派生類也是抽象類。可以認為,純虛函數一定程度上強制派生類重寫虛函數,因為不重寫不實例化出對象。

在這里插入圖片描述

六、多態的原理

還是看一開始的例子:

class Person
{
public:virtual void BuyTicket(){cout << "全價" << endl;}
};class Student : public Person
{
public:virtual void BuyTicket(){cout << "打折" << endl;}
};class Soldier : public Person
{
public:virtual void BuyTicket(){cout << "優先" << endl;}
};
int main()
{Person per;Student stu;Soldier sol;Person* ptr;ptr = &per;ptr->BuyTicket(); ptr = &stu;ptr->BuyTicket();ptr = &sol;ptr->BuyTicket();return 0;
}

調試觀察,可以發現:
在這里插入圖片描述
每一個對象的第一個成員都是一個叫__vfptr的指針(有些平臺可能會把它放在最后一個),這個指針叫做虛函數表指針,指向這個類的虛函數表。一個含有虛函數的類中都至少有一個虛函數表指針,一個類所有虛函數的地址都會存在一個虛函數表中,虛函數表實際上就是函數指針數組,也稱虛表。

  • 基類和每個派生類都有自己獨立的虛函數表。派生類會繼承基類的虛函數表指針,但是這個虛函數表指針和基類的虛函數表指針不是同一個指針,指向的虛表也就不是同一個。
  • 派生類重寫了基類的虛函數后,派生類的虛表中對應的原基類虛函數就會被覆蓋成派生類重寫的虛函數地址。這也是為什么重寫也可以稱為覆蓋。

基于這樣的原理,基類的指針或引用調用基類和派生類的同一個虛函數時,其實就是從它們各自的__vfptr找到各自的虛函數表,再找到這個虛函數。但由于派生類的虛函數表中這個虛函數已經被重寫(覆蓋),調用后也就有不同的結果了。這就是多態實現的原理。

本篇完,感謝閱讀。

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

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

相關文章

2025.5.27學習日記 linux三劍客 sed與正則表達式

sed是Stream Editor(字符流編輯器)的縮寫,簡稱流編輯器。 sed是操作、過濾和轉換文本內容的強大工具。 常用功能包括結合正則表達式對文件實現快速增刪改查 , 其中查詢的功能中最常用的兩大功能是過 濾 ( 過濾指定字符串)、取行(取出指定行)。 注意sed和awk使用單引號,雙引號…

文科小白學習Linux系統之安全管理

目錄 前言 一、SELinux安全上下文 1、SELinux 簡介 2、基礎操作命令 1. 查看SELinux狀態 2. 切換工作模式 3、安全上下文&#xff08;Security Context&#xff09; 1. 查看上下文 2. 修改上下文 chcon命令 semanage 命令 4、SELinux布爾值&#xff08;Booleans&am…

企業內訓系統源碼開發詳解:直播+錄播+考試的混合式學習平臺搭建

在企業數字化轉型的大潮中&#xff0c;員工培訓早已不再是傳統教室中的一場場“走過場”&#xff0c;而是通過技術驅動的“系統化能力提升”。尤其在知識更新換代加速、競爭壓力日益激烈的背景下&#xff0c;企業越來越傾向于建設自主可控、功能靈活、支持多種學習形態的內訓平…

智能化報銷與精細化管理:購物小票識別系統全面提升企業運營效率

在現代企業管理中&#xff0c;購物小票的處理一直是財務和運營管理中的一項挑戰。尤其在企業費用報銷、會員管理、庫存監控等環節&#xff0c;手動整理與核對小票不僅耗時費力&#xff0c;還容易產生錯誤。隨著人工智能技術的發展&#xff0c;企業亟需一種高效、智能的解決方案…

毫秒級數據采集的極致優化:如何用C#實現高性能、無冗余的實時文件寫入?

在工業控制、通信系統或高頻交易領域&#xff0c;毫秒級數據采集的精度直接決定系統性能。但一個棘手問題常被忽視&#xff1a;如何處理同一毫秒內的重復數據&#xff1f; 若簡單寫入所有數據&#xff0c;會導致文件臃腫、分析效率驟降&#xff1b;若處理不當&#xff0c;又可能…

NLua性能對比:C#注冊函數 vs 純Lua實現

引言 在NLua開發中&#xff0c;我們常面臨一個重要選擇&#xff1a;將C#函數注冊到Lua環境調用&#xff0c;還是直接在Lua中實現邏輯&#xff1f; 直覺告訴我們&#xff0c;C#作為編譯型語言性能更高&#xff0c;但跨語言調用的開銷是否會影響整體性能&#xff1f;本文通過基準…

go并發與鎖之sync.Mutex入門

sync.Mutex 原理&#xff1a;一個共享的變量&#xff0c;哪個線程握到了&#xff0c;哪個線程可以執行代碼 功能&#xff1a;一個性能不錯的悲觀鎖&#xff0c;使用方式和Java的ReentrantLock很像&#xff0c;就是手動Lock&#xff0c;手動UnLock。 使用例子&#xff1a; v…

【HarmonyOS5】DevEco Studio 使用指南:代碼閱讀與編輯功能詳解

?本期內容&#xff1a;【HarmonyOS5】DevEco Studio 使用指南&#xff1a;代碼閱讀與編輯功能詳解 &#x1f3c6;系列專欄&#xff1a;鴻蒙HarmonyOS&#xff1a;探索未來智能生態新紀元 文章目錄 前言代碼閱讀代碼導航功能代碼折疊語法高亮跨語言跳轉代碼查找 快速查閱API接口…

【Python 深度學習】1D~3D iou計算

一維iou 二維 import numpy as npdef iou_1d(set_a, set_b):# 獲得集合A和B的邊界 x1, x2 set_ay1, y2 set_b# 計算交集的上下界low max(x1,y1)high - min(x2, y2)# 計算交集if high - low < 0:inter 0else:inter high - low# 計算并集union (x2 -x1) (y2 - y1) - in…

SpringBoot Controller接收參數方式, @RequestMapping

一. 通過原始的HttpServletRequest對象獲取請求參數 二. 通過Spring提供的RequestParam注解&#xff0c;將請求參數綁定給方法參數 三. 如果請求參數名與形參變量名相同&#xff0c;直接定義方法形參即可接收。(省略RequestParam) 四. JSON格式的請求參數(POST、PUT) 主要在PO…

智能防護實戰:從攻擊成本看企業安全降本增效

1. 網絡攻擊的低成本與高回報陷阱 暗網中&#xff0c;一次完整的網絡釣魚攻擊僅需30美元/月起步&#xff0c;而勒索軟件攻擊成本平均1000美元&#xff0c;卻能導致企業損失高達445萬美元&#xff08;IBM 2023年數據&#xff09;。例如&#xff0c;信用卡信息每條僅售10美元&am…

大語言模型 20 - MCP 在客戶端中使用 Cursor Cline 中配置 MCP 服務

MCP 基本介紹 官方地址&#xff1a; https://modelcontextprotocol.io/introduction “MCP 是一種開放協議&#xff0c;旨在標準化應用程序向大型語言模型&#xff08;LLM&#xff09;提供上下文的方式。可以把 MCP 想象成 AI 應用程序的 USB-C 接口。就像 USB-C 提供了一種…

MySQL 在 CentOS 7 環境下的安裝教程

&#x1f31f; 各位看官好&#xff0c;我是maomi_9526&#xff01; &#x1f30d; 種一棵樹最好是十年前&#xff0c;其次是現在&#xff01; &#x1f680; 今天來學習Mysql的相關知識。 &#x1f44d; 如果覺得這篇文章有幫助&#xff0c;歡迎您一鍵三連&#xff0c;分享給更…

WPF的基礎設施:XAML基礎語法

XAML基礎語法 1 控件聲明與屬性設置1.1 特性語法&#xff08;Attribute Syntax&#xff09;1.2 屬性元素語法&#xff08;Property Element Syntax&#xff09;1.3 特殊值標記擴展 2 x:Name與Name的區別3 注釋與代碼折疊4 實用技巧集合5 常見錯誤排查 XAML( Extensible Applic…

機器學習筆記【Week3】

一、邏輯回歸&#xff08;Logistic Regression&#xff09; 與線性回歸的區別&#xff1a; 問題類型輸出類型舉例回歸問題連續實數房價預測、氣溫預測分類問題離散類別&#xff08;0 或 1&#xff09;是否患病、是否點擊廣告、是否合格 我們希望構建一個模型&#xff0c;根據…

6.4.2_3最短路徑問題_Floyd算法

Floyd弗洛伊德 膜拜大佬&#xff0c;給大佬鞠躬鞠躬鞠躬。。。。。。。。。 Floyd算法 ----解決頂點間的最短路徑&#xff1a; 過程&#xff1a; 如下&#xff1a; 初始化(沒有中轉點)&#xff1a;2個鄰接矩陣A和path&#xff0c;第一個是沒有中轉點的2個頂點之間的最短路徑…

uniapp|實現多端圖片上傳、拍照上傳自定義插入水印內容及拖拽自定義水印位置,實現水印相機、圖片下載保存等功能

本文以基礎視角,詳細講解如何在uni-app中實現圖片上傳→水印動態編輯→圖片下載的全流程功能。 目錄 引言應用場景分析(社交媒體、內容保護、企業素材管理等)uniapp跨平臺開發優勢核心功能實現?圖片上傳模塊多來源支持:相冊選擇(`uni.chooseImage`)與拍照(`sourceType:…

2021年認證杯SPSSPRO杯數學建模B題(第二階段)依巴谷星表中的畢星團求解全過程文檔及程序

2021年認證杯SPSSPRO杯數學建模 B題 依巴谷星表中的畢星團 原題再現&#xff1a; 依巴谷衛星&#xff08;High Precision Parallax Collecting Satellite&#xff0c;縮寫為 Hip-parcos&#xff09;&#xff0c;全稱為“依巴谷高精度視差測量衛星”&#xff0c;是歐洲空間局發…

行為型:解釋器模式

目錄 1、核心思想 2、實現方式 2.1 模式結構 2.2 實現案例 3、優缺點分析 4、適用場景 5、注意事項 1、核心思想 目的&#xff1a;針對某種語言并基于其語法特征創建一系列的表達式類&#xff08;包括終極表達式與非終極表達式&#xff09;?&#xff0c;利用樹結構模式…

Redis分布式緩存核心架構全解析:持久化、高可用與分片實戰

一、持久化機制&#xff1a;數據安全雙引擎 1.1 RDB與AOF的架構設計 Redis通過RDB&#xff08;快照持久化&#xff09;和AOF&#xff08;日志持久化&#xff09;兩大機制實現數據持久化。 ? RDB架構&#xff1a;采用COW&#xff08;寫時復制&#xff09;技術&#xff0c;主進程…