關于C++多態的復習總結

多態

簡介: 面向對象的三大特性之一,多態顧名思義即具有多種形態,即去執行某個行為時,當不同的對象去執行時會產生不同的狀態

構成多態的條件

條件一

必須通過基類(父類)的指針或者引用調用虛函數(函數被virtual所修飾)
tips:父類的指針或引用要指向或引用子類對象

virtual void test(){}

條件二

被調用的函數必須是虛函數,且派生類必須對基類的虛函數進行重寫
tips:破壞任意一個條件都會導致無法構成多態

  • 虛函數的重寫是接口繼承(普通函數的重寫是實現繼承)
    ps:普通函數的重寫是外殼不同,將函數體(實現)繼承下來
  • 子類中的虛函數只是對父類的接口的一個聲明(聲明必須保持一致,故函數名,形參以及返回值都相同),重寫只是將父類函數的“外殼”拿了下來,然后再在這個“外殼”內填充函數體(重寫的是實現)

滿足上述兩個條件即構成多態:即通過基類(父類)的指針或者引用調用虛函數
子類沒重寫也會進行運行時決議(多態),但是由于沒有進行覆蓋,仍舊調用的是父類的虛函數

虛函數重寫/覆蓋的條件

虛函數+三同(函數名,形參以及返回值都相同),不符合重寫的條件即構成隱藏

  • tips1:子類重寫虛函數時,是否添加virtual修飾對多態不影響

  • tips2:重寫的協變,協變即返回值可以不同,但要求父子函數的返回值必須分別是父子關系的指針或者引用
    如下述兩種方式都是可以構成多態(此種用途并不多,了解即可)

    class Person
    {
    public://virtual void BuyTicket(char) { cout << "買票-全價" << endl; }/*virtual Person* BuyTicket(int) { cout << "買票-全價" << endl;return this;}*///假設A是B的父類,即下述也構成多態virtual A* BuyTicket(int){cout << "買票-全價" << endl;return nullptr;}
    };class Student : public Person {
    public:// 虛函數重寫/覆蓋條件 : 虛函數 + 三同(函數名、參數、返回值)// 不符合重寫,就是隱藏關系// 特例1:子類虛函數不加virtual,依舊構成重寫 (實際最好加上)// 特例2:重寫的協變。返回值可以不同,要求必須時父子關系的的指針或者引用/*virtual Student* BuyTicket(int){cout << "買票-半價" << endl;return this;}*/virtual B* BuyTicket(int){ cout << "買票-半價" << endl;return nullptr;}
    };
    

構成多態的原理

虛表(虛函數表)

  • 虛表內存儲著所有的虛函數(函數地址),虛表本質是一個數組,數組內存著的都為函數指針

    • 父類對象和子類對象里各自都有各自的虛表
      子類對象的虛表是拷貝父類對象得來
    • 當子類重寫虛函數時,則修改了子類自己的虛表對應的函數(覆蓋成子類重寫后的函數)

虛表指針

當類內存在了virtual修飾的虛函數,則該類內會默認生成一個虛表指針(__vfptr)指向一張虛表
虛表指針在vs環境下,默認是在對象的頭4個字節或者頭8個字節(可以通過取出該字節的內容所指向的地址來打印虛表)
ps: 虛函數表是編譯時即生成的,在構造函數中進行初始化虛表指針,對象中存儲的為虛表指針,虛表存儲位置大致在常量區(編譯階段即生成好了)

  • tips:可以按下述方式嘗試打印虛表內的內容

    class Person 
    {
    public:virtual void BuyTicket() { cout << "Person::買票-全價" << endl;}virtual void Func1(){cout << "Person::Func1()" << endl;}
    };class Student : public Person {
    public:virtual void BuyTicket() { cout << "Student::買票-半價" << endl;}virtual void Func2(){cout << "Student::Func2()" << endl;}
    };typedef void(*VFPTR)();//void PrintVFTable(VFPTR table[])
    //void PrintVFTable(VFPTR* table, size_t n)
    void PrintVFTable(VFPTR* table)
    {//vs下,虛表末尾會加上空指針作為標識for (size_t i = 0; table[i] != nullptr; ++i)//for (size_t i = 0; i < n; ++i){printf("vft[%d]:%p->", i, table[i]);//table[i]();VFPTR pf = table[i];pf();}cout << endl;
    }
    int main()
    {// 同一個類型的對象共用一個虛表Person p1;Person p2;// vs下 不管是否完成重寫,子類虛表跟父類虛表都不是同一個Student s1;Student s2;//取到對象頭四個字節的虛表指針中的函數地址,再強轉成函數指針(因為本身就是函數地址)PrintVFTable((VFPTR*)*(int*)&s1);PrintVFTable((VFPTR*)*(int*)&p1);
    }
    

普通多繼承下的情況

  • 多繼承中,子類新增的虛函數會被放到多繼承下來的第一個對象的虛表里
    多繼承的對象有幾個,則子類中有多少個虛表(從父類繼承得來)
    • 多繼承下,子類自身的虛函數會被放到多繼承第一個對象的虛表中
    • 多繼承下,不同的父類指針指向子類對象并調用虛函數時,底層實現略有不同,不過到底也是相同的
      因為調用函數時,本質也要傳入指向對象的地址,繼承的兩個基類所在的地址不同,故此底層跳轉步驟不盡相同
      • 如果是第一個繼承的對象,則是直接進行call函數地址然后jump到函數實現
      • 如果是第二個繼承的對象,則會先call指令,然后會先偏移到子類對象的首地址處,再進行jump

菱形繼承下的情況

  • 最開始菱形繼承中,如果菱形繼承的兩個父類沒有額外的虛函數,則是共用基類的虛表(通過虛基表指向)

  • 如果菱形繼承下,兩個父類還有額外的虛函數,則父類其還會擁有自己的虛表指針(指向虛表)

  • 虛基表:虛繼承中產生的虛基類表(解決數據冗余和二義性)

    • 虛基表的記錄的內容其一是當前派生類的虛基表與其虛表的偏移量(如果不存在額外的虛表則偏移量為0)
      為了讓派生類能夠找到其虛表的位置
    • 其二是記錄虛基類與其派生類在當前對象模型中的偏移地址
  • 只要是虛函數,函數地址都會放入虛表中,無論是否被重寫,子類的虛表中既有父類的虛函數,也有子類的虛函數
    tips:同一個類型的對象共用一個虛表,(vs環境)子類和父類的虛表不管是否完成重寫,二者虛表都不是同一個

總結

多態的本質即當符合多態的兩個條件,調用時則會到指向對象的虛表中找到對應的函數地址進行調用
故此多態的調用時運行時才通過虛表確定了函數的地址,編譯時并不知道會自身會指向父類還是子類的對象,運行到了才會到實際指向對應對象的虛表內找到函數地址再進行調用
(普通函數的調用是調用call指令,在編譯鏈接時確定了函數的地址(聲明+定義),運行時直接調用)


析構函數的重寫

父類的析構函數在繼承中建議添加virtual修飾,完成虛函數的重寫

class Person{public:virtual ~Person(){cout << "~Person()" << endl;}
}
class Student{public:virtual ~Student(){cout << "~Student()" << endl;}
}
int main(){Person* ptr1 = new Person();delete ptr1;//如果不構成多態,則是什么類型即調用什么類型的析構函數,不符合預期Person* ptr2 = new Student();delete ptr2;//構成多態則指向父類調用父類析構,指向子類調用子類析構,子類析構后再自動調用父類的析構函數
}
  • 編譯器生成的析構函數的作用(同上)
    自己調用自己的析構,父類對象去調用父類的析構
    • 由于多態的需要,析構函數的名字會被統一處理成destructor()
      所以析構函數也會與父類構成隱藏
    • 由于語法與編譯器要求,構造時需要先構造父類,再構成子類,析構則需要保證先析構子類,再析構父類,所以自定義析構函數時不需要顯式調用父類的析構,編譯器會在析構完子類后自動調用父類的析構

override和final關鍵字

  • 當一個虛函數不想被重寫,則使用final進行修飾(使用場景極少)
  • override用于修飾子類的虛函數,其對子類的虛函數是否重寫進行了強制性語法檢查

重載、覆蓋(重寫)、隱藏(重定義)的對比

  • 重載
    • 兩個函數在同一作用域
    • 函數名相同,參數不同(個數,類型,順序)
  • 重寫(覆蓋)
    • 兩個函數分別在基類和派生類的作用域
    • 函數名,參數,返回值都必須相同(協變屬于例外)
    • 兩個函數必須都是虛函數(用virtual修飾)
  • 重定義(隱藏)
    • 兩個函數分別在基類和派生類的作用域
    • 函數名相同
    • 基類與派生類的同名函數不構成重寫即為重定義(隱藏)

抽象類

在虛函數后面寫上=0,則這個函數為純虛函數,包含這個純虛函數的類即為抽象類(也被成為接口類)

  • 抽象類基類是無法實例化出對象的
  • 子類繼承了純虛類后,子類必須得進行虛函數的重寫,否則也無法實例化對象

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

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

相關文章

寧夏銀川市起名專家的老師顏廷利:死神(死亡)并不可怕,可怕的是...

在中國優秀傳統文化之中&#xff0c;漢語‘巳’字與‘四’同音&#xff0c;在阿拉伯數字里面&#xff0c;通常用‘4’來表示&#xff1b; 湖南長沙、四川成都、重慶、寧夏銀川最靠譜最厲害的起名大師的老師顏廷利教授指出&#xff0c;作為漢語‘九’字&#xff0c;倘若是換一個…

FreeRTOS中斷管理

FreeRTOS中斷管理 基于STM32_stm32 freertos 按鍵中斷-CSDN博客 更加詳情請看以上鏈接↑ 中斷優先級 任何中斷的優先級都大于任務! 在我們的操作系統,中斷同樣是具有優先級的,并且我們也可以設置它的優先級,但是他的優先 級并不是從 0~15 ,默認情況下它是從 5~15 ,…

[ACTF新生賽2020]SoulLike

沒見過的錯誤&#xff1a; ida /ctg目錄下的hexrays.cfg文件中的MAX_FUNCSIZE64 改為 MAX_FUNCSIZE1024 然后就是一堆數據 反正就是12個字符 from pwn import * flag"actf{" k0 for n in range(12):for i in range(33,127):pprocess("./SoulLike")_flag…

94.二叉樹的中序遍歷

刷算法題&#xff1a; 第一遍&#xff1a;1.看5分鐘&#xff0c;沒思路看題解 2.通過題解改進自己的解法&#xff0c;并且要寫每行的注釋以及自己的思路。 3.思考自己做到了題解的哪一步&#xff0c;下次怎么才能做對(總結方法) 4.整理到自己的自媒體平臺。 5.再刷重復的類…

Python爬蟲入門:網絡世界的寶藏獵人

今天阿佑將帶你踏上Python的肩膀&#xff0c;成為一名網絡世界的寶藏獵人&#xff01; 文章目錄 1. 引言1.1 簡述Python在爬蟲領域的地位1.2 闡明學習網絡基礎對爬蟲的重要性 2. 背景介紹2.1 Python語言的流行與適用場景2.2 網絡通信基礎概念及其在數據抓取中的角色 3. Python基…

今日總結2024/5/13

今日學習了01背包求具體方案的方法 Acwing.12 背包問題求具體方案 由于背包是從小到大枚舉物品&#xff0c;只能從后往前判斷是從哪個狀態遞推過來的&#xff0c;而該題要求按字典序順序輸出字典序最小的最優方案 因此要將物品從大到小枚舉&#xff0c;判斷時從小到大判斷是…

在Windows上有哪些好用的網絡抓包工具?

2024年5月12日&#xff0c;周日上午 在Windows上&#xff0c;有多種好用的網絡抓包工具&#xff0c;以下是一些常見的選項&#xff1a; Wireshark&#xff1a; Wireshark 是一款功能強大的網絡協議分析工具&#xff0c;它可以捕獲并分析計算機網絡上的數據包。它支持廣泛的協議…

ssm+vue的公務用車管理智慧云服務監管平臺查詢統計(有報告)。Javaee項目,ssm vue前后端分離項目

演示視頻&#xff1a; ssmvue的公務用車管理智慧云服務監管平臺查詢統計&#xff08;有報告&#xff09;。Javaee項目&#xff0c;ssm vue前后端分離項目 項目介紹&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&…

求階乘n!末尾0的個數溢出了怎么辦

小林最近遇到一個問題&#xff1a;“對于任意給定的一個正整數n&#xff0c;統計其階乘n&#xff01;的末尾中0的個數”&#xff0c;這個問題究竟該如何解決&#xff1f; 先用n5來解決這個問題。n的階乘即n!5!5*4*3*2*1120&#xff0c;顯然應該為2個數相乘等于10才能得到一個結…

軟件測試自動化:加速測試,提升效率

目錄 測試自動化的內涵 測試自動化的原理 測試工具的分類和選擇 自動化測試的引入 在當今的軟件開發中&#xff0c;測試自動化已經成為提升效率和確保軟件質量的關鍵環節。測試自動化是指使用軟件工具和腳本來執行重復的測試任務&#xff0c;從而減輕人工測試的負擔&#x…

量化交易包含些什么?

我們講過許多關于量化交易的內容&#xff0c;但是量化交易具體可以做些什么&#xff1f;很多朋友都還不清楚&#xff0c;我們詳細來探討下&#xff01; 第一&#xff1a;什么是量化交易&#xff1f; 量化交易是一種利用先進的數學模型和計算機技術&#xff0c;從大量的歷史數…

制造業精益生產KPI和智慧供應鏈管理方案和實踐案例分享

隨著工業4.0的推進和國家對制造業高質量發展的重視&#xff0c;工業數據已躍升為生產經營活動中不可或缺的核心要素&#xff0c;同時&#xff0c;工業數據也是形成新質生產力的優質生產要素&#xff0c;助力企業實現高效精益生產。 工業數據在制造業中的作用不可忽視&#xff…

常見地圖坐標系間的轉換算法JavaScript實現

文章目錄 ?? 不同的地圖廠商使用不同的坐標系來表示地理位置。以下簡述:?? 前置常量和方法:?? BD-09轉GCJ-02(百度轉谷歌、高德)?? GCJ-02轉BD-09(谷歌、高德轉百度)?? WGS84轉GCJ-02(WGS84轉谷歌、高德)?? GCJ-02轉WGS84(谷歌、高德轉WGS84)?? BD-09轉wgs84坐…

Linux: 默認進程介紹

進程名稱介紹systemdSystemd 可以管理所有系統資源。不同的資源統稱為 Unit&#xff08;單位&#xff09;。 Unit 一共分成12種。 systemctl list-units命令可以查看當前系統的所有 Unitkthreaddkthreadd進程由idle通過kernel_thread創建&#xff0c;并始終運行在內核空間, 負責…

H5利用微信開放標簽喚起用戶手機APP

APP殼子分享網頁到微信&#xff0c;被分享人在微信打開網頁后&#xff0c;利用公眾號配置微信開放標簽[wx-open-launch-app]&#xff0c;實現喚起APP 一、Vue2.x&#xff08;2.6.11&#xff09; 1. main.js // main.jsimport Vue from vue;Vue.config.ignoredElements [wx-o…

Hbase基礎操作Demo(Java版)

一、前置條件 HBase服務&#xff1a;【快捷部署】023_HBase&#xff08;2.3.6&#xff09;開發環境&#xff1a;Java&#xff08;1.8&#xff09;、Maven&#xff08;3&#xff09;、IDE&#xff08;Idea 或 Eclipse&#xff09; 二、相關代碼 代碼結構如上圖中①和② pom.x…

IO—消息隊列+管道

使用消息隊列實現的2個終端之間的互相聊天 并使用信號控制消息隊列的讀取方式: 當鍵盤按ctrlc的時候&#xff0c;切換消息讀取方式&#xff0c;一般情況為讀取指定編號的消息&#xff0c;按ctr1c之后&#xff0c;指定的編號不讀取&#xff0c;讀取其他所有編號的消息 wftok.c …

vue項目中使用websocke即時通訊實現系統公告實時獲取并提醒

一、使用場景 發布者設置需要發布的公告內容、公告接收用戶和發布時間&#xff0c;到達發布時間時及時通知提醒已登錄系統用戶&#xff0c;使用websocke來實現前端與服務器保持長連接&#xff0c;以便實時過去公告信息。 WebSocket是一種在單個TCP連接上進行全雙工通信的協議…

調用Mertc的接口

概述 metaRTC5.0版本 API進行了重構&#xff0c;本篇文章將介紹webrtc傳輸調用流程和例子。 metaRTC5.0版本提供了C和純C兩種接口。 ICE設置 iceCandidateType參數可以在配置文件yang_config.ini中配置&#xff0c;也可以在程序中賦值。 iceCandidateType0 //0:host 1:stun 2…

2024最新大廠C++面試真題合集,大廠面試百日沖刺 bay9

騰訊實習 指針常量和常量指針 常量指針&#xff08;const Type* ptr&#xff09;&#xff1a;指針指向的內容不能被改變&#xff0c;但指針本身可以改變指向。 指針常量&#xff08;Type* const ptr&#xff09;&#xff1a;指針自身的值即內存地址不能改變&#xff0c;但指向…