【C++進階十】多態深度剖析

【C++進階十】多態深度剖析

  • 1.多態的概念及條件
  • 2.虛函數的重寫
  • 3.重寫、重定義、重載區別
  • 4.C++11新增的override 和final
  • 5.抽象類
  • 6.虛表指針和虛表
    • 6.1什么是虛表指針
    • 6.2指向誰調用誰,傳父類調用父類,傳子類調用子類
  • 7.多態的原理
  • 8.單繼承的虛表狀態
  • 9.多繼承的虛表狀態
  • 10.菱形繼承
  • 11.菱形虛繼承

1.多態的概念及條件

繼承是實現多態的前提
通俗來說,多態就是多種狀態,父子對象完成相同任務會產生不同的結果
例如:成年人買火車票是全價票,學生買票是折扣票

在繼承中構成多態要有兩個條件:

  1. 必須通過父類的指針引用去調用
  2. 被調用的函數必須是虛函數,且完成虛函數的重寫

在這里插入圖片描述

2.虛函數的重寫

什么是虛函數:

被virtual修飾的類成員函數稱為虛函數

什么是虛函數的重寫(覆蓋):

三同:父子虛函數的函數名返回值類型參數相同
(參數的缺省值可以不同)

傳遞不同的對象調用不同的函數
傳父類調用的是父類的虛函數
傳子類調用的是子類的虛函數

虛函數重寫的例外:

  1. 子類的虛函數可以不加virtual
    重寫體現了接口繼承:子類把函數的聲明繼承下來,重寫的是函數的實現,所以不寫virtual也可以滿足多態的條件
    在這里插入圖片描述

  2. 協變:子類與父類的虛函數返回值類型不同,但必須滿足父類虛函數返回父類對象的指針或引用,子類虛函數返回子類對象的指針或引用
    在這里插入圖片描述
    這個父子類指針也可以是自己的父子類,也可以是指其他類型的父子類:
    在這里插入圖片描述

子類的虛函數可以不加virtual可以防止析構函數出錯:
正確使用析構函數:在這里插入圖片描述
父類和子類的虛函數的析構函數函數名并不相同卻依然構成虛函數的重寫,因為析構函數在多態中會被編譯器修改成同一個名字
為什么會變成同一個名字:析構函數會因為父子類關系,在子類調用析構后會自動調用父類的析構,如果父子的析構函數不是虛函數,調用析構時就不會調用子類的析構,即使我們指向了子類
在這里插入圖片描述
為什么沒有調用到子類:因為delete的內部構成是:析構函數和operator delete(),而operator delete 有一個特點就是調用delete的指針是什么類型的,就會調用什么類型的析構函數,上圖的兩個指針p1和p2都是父類person類型,所以就都調用了父類person的析構函數,沒有調動子類的析構函數
實際使用結果如下:
在這里插入圖片描述
所以想要指向父類調用父類析構,指向子類調用子類析構,就需要編譯器把析構函數的名字進行了統一,滿足了虛函數重寫的三同:父子虛函數的函數名返回值類型參數相同
子類可以不寫virtual,只要父類加上了virtual就可以進行虛函數的重寫,但是不太建議
在這里插入圖片描述

3.重寫、重定義、重載區別

在這里插入圖片描述

4.C++11新增的override 和final

overrride:檢查子類虛函數是否重寫了父類的某個虛函數,如果沒有重寫編譯報錯
在這里插入圖片描述

final:修飾虛函數,表示該虛函數不能被重寫
在這里插入圖片描述

5.抽象類

在虛函數后面寫上=0,這個虛函數被稱為純虛函數,包含純虛函數的類叫做抽象類,抽象類不能實例化對象
在這里插入圖片描述
若創建一個子類繼承抽象類,則該子類也包含純虛函數,子類也會變成抽象類,所以子類創建對象也會報錯
在這里插入圖片描述

6.虛表指針和虛表

6.1什么是虛表指針

sizeof(Base) 大小是多少?
在這里插入圖片描述
以結構體的內存對齊考慮,在32位機器下,大小應為8字節,但是實際上為12字節

在這里插入圖片描述
_vfptr代表虛函數表指針
加上虛表指針,內存對齊后字節大小為12

6.2指向誰調用誰,傳父類調用父類,傳子類調用子類

  1. 父類對象的虛表與子類對象的虛表沒有任何關系,這是兩個不同的對象
  2. 通過虛表指針和虛函數表就可以實現多態性,即可以在運行時確定應該調用哪個類的虛函數
  3. 虛表指針是類級別的,而不是函數級別的
  4. 每個類只有一個虛表指針,指向其對應的虛函數表,但是多繼承的時候,就會可能有多張虛表
  5. 每一個類中的虛函數在虛表中都有地址

在這里插入圖片描述
如圖所示, 圖中父子類各自的虛表指針指向的虛表以及虛表內部的地址并不相同,這證明了父類的虛表指針和子類的虛表指針指向的虛表并不是同一個

7.多態的原理

class Person
{
public:virtual void BuyTicket(){cout << "成人:全價票" << endl;}
};class Student : public Person
{
public:virtual void BuyTicket() {cout << "學生:半價票" << endl;}
};void func(Person* p)
{p->BuyTicket();//父類指針指向父類對象A(或子類對象B)
}int main()
{Person A;func(&A);//傳父類對象Student B;func(&B);//傳子類對象return 0;
}

父類對象內的虛函數放進了父類的虛表,子類對象繼承了父類的同時也繼承了父類的虛表,如果子類有虛函數的重寫,那么父類的虛表內有父類的虛函數,子類的虛表內有子類的虛函數(重寫后的虛函數)
指向誰調用誰,父類指針指向父類對象,則從父類的虛表中找到父類的虛函數;父類指針指向子類對象,則從子類的虛表中找到子類的虛函數,因此產生了指向父類調用父類,指向子類調用子類的現象

不是多態則是在編譯時就是已經指向了某個地方,而不是因為指向誰調用了誰
多態的本質在底層看來就是在虛表內尋找虛函數

虛函數和普通函數一樣都是存在于代碼段中的,而不是存在虛表內部的,虛表內部僅僅儲存了虛函數的地址,而虛表儲存在常量區

虛函數表 本質是一個虛函數指針數組
子類的虛表是由父類的虛表拷貝過來的,再向其中填入新的地址,所以造成覆蓋
為什么父類對象不可以實現多態,必須是父類的指針或引用?
若為父類對象,就會把子類中屬于父類的那一部分拷貝給父類,有可能把子類的虛表也拷貝給父類,若拷貝成功,則父類對象的虛表就不知道是父類的虛表還是子類的虛表了
若為指針或者引用,將子類中屬于父類那一部分切出來,使指針指向屬于父類那一部分,或者作為屬于父類那一部分的別名,子類的虛表還是子類的

8.單繼承的虛表狀態

class A
{
public:virtual void func1(){cout << "A::func1" << endl;}virtual void func2(){cout << "A::func2" << endl;}
};class B : public A
{
public:virtual void func1(){cout << "B::func1" << endl;}virtual void func3(){cout << "B::func3" << endl;}virtual void func4(){cout << "B::func4" << endl;}
};int main()
{A a;B b;return 0;
}

在這里插入圖片描述
可以看到子類的虛表不正常,因為每個類中的虛函數在虛表中都要出現,而子類虛表里少了兩個虛函數的地址,func1是重寫的,func2是繼承的(沒有重寫),而func3和func4不見了,子類自己的虛函數消失了
這里可以解釋為一種bug:是Visual Studio監視窗口的bug
也可也理解為:子類的虛表實際上是拷貝了父類的虛表,重寫的部分進行覆蓋,沒有重寫的部分原模原樣地拷貝,可以說是子類的虛表被隱藏了

9.多繼承的虛表狀態

class A
{
public:virtual void func1(){cout << "A::func1" << endl;}virtual void func2(){cout << "A::func2" << endl;}
private:int a;
};class B
{
public:virtual void func1(){cout << "B::func1" << endl;}virtual void func2(){cout << "B::func2" << endl;}
private:int b;
};class C : public A, public B
{
public:virtual void func1(){cout << "C::func1" << endl;}virtual void func3(){cout << "C::func3" << endl;}
private:int c;
};int main()
{C temp;return 0;
}

在這里插入圖片描述
在這里插入圖片描述
C由兩個部分構成,第一個是繼承了A, 一個類中只有一個虛表指針,所以A內部有一個虛表指針和一個int類型的對象a,第二個是繼承了B, 一個類中只有一個虛表指針,所以B內部有一個虛表指針和一個int類型的對象b
C類的虛函數func3放到了繼承的第一個類A的虛表內部
所以C中有兩個虛表指針,同時這里的兩個虛表指針不能合二為一,這關系于切片問題
所以多繼承內部可能會有多個虛表指針

10.菱形繼承

class A
{
public:virtual void func1(){cout << "A::func1" << endl;}
private:int a;
};class B : public A
{
public:virtual void func2(){cout << "B::func2" << endl;}
private:int b;
};class C : public A
{
public:virtual void func3(){cout << "C::func3" << endl;}
private:int c;
};class D : public B,public C
{
public:virtual void func4(){cout << "D::func4" << endl;}
private:int a;
};int main()
{D temp;return 0;
}

菱形繼承和多繼承沒區別,同時func4放入了B的虛表內部
D類對象temp有兩張虛表,分別是B和C的虛表

11.菱形虛繼承

class A
{
public:virtual void func1(){cout << "A::func1" << endl;}int _a = 1;
};class B : virtual public A
{
public:virtual void func1(){cout << "B::func1" << endl;}int _b = 2;
};class C : virtual public A
{
public:virtual void func1(){cout << "C::func1" << endl;}int _c = 3;
};class D : public B,public C
{
public:virtual void func1(){cout << "D::func1" << endl;}virtual void func2(){cout << "D::func2" << endl;}int _d = 4;
};int main()
{D temp;return 0;
}

在這里插入圖片描述

A的虛表由B、C共享
D自己新增的func2需要虛表,但D對象中的B沒有的虛表
虛基表存儲偏移量,幫助B、C找到A

注意:上述的B、C沒有新增額外虛函數,如果有新增,則D的虛表消失,B和C各有一張虛表,D的虛函數放入B的虛表內,共計三張虛表

class A
{
public:virtual void func1(){cout << "A::func1" << endl;}int _a = 1;
};class B : virtual public A
{
public:virtual void func1(){cout << "B::func1" << endl;}virtual void func3(){cout << "B::func3" << endl;}int _b = 2;
};class C : virtual public A
{
public:virtual void func1(){cout << "C::func1" << endl;}virtual void func4(){cout << "C::func4" << endl;}int _c = 3;
};class D : public B,public C
{
public:virtual void func1(){cout << "D::func1" << endl;}virtual void func2(){cout << "D::func2" << endl;}int _d = 4;
};int main()
{D temp;return 0;
}

在這里插入圖片描述

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

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

相關文章

面向網絡安全的開源 大模型-Foundation-Sec-8B

1. Foundation-Sec-8B 整體介紹 Foundation-Sec-8B 是一個專注于網絡安全領域的大型語言模型 (LLM),由思科的基礎人工智能團隊 (Foundation AI) 開發 。它基于 Llama 3.1-8B 架構構建,并通過在一個精心策劃和整理的網絡安全專業語料庫上進行持續預訓練而得到增強 。該模型旨在…

Python爬蟲的基礎用法

Python爬蟲的基礎用法 python爬蟲一般通過第三方庫進行完成 導入第三方庫&#xff08;如import requests &#xff09; requests用于處理http協議請求的第三方庫,用python解釋器中查看是否有這個庫&#xff0c;沒有點擊安裝獲取網站url&#xff08;url一定要解析正確&#xf…

WHAT - Tailwind CSS + Antd = MetisUI組件庫

文章目錄 Tailwind 和 Antd 組件庫MetisUI 組件庫 Tailwind 和 Antd 組件庫 在 WHAT - Tailwind 樣式方案&#xff08;不寫任何自定義樣式&#xff09; 中我們介紹了 Tailwind&#xff0c;至于 Antd 組件庫&#xff0c;我們應該都耳熟能詳&#xff0c;官網地址&#xff1a;htt…

Day 4:牛客周賽Round 91

好久沒寫了&#xff0c;問題還蠻多的。聽說這次是苯環哥哥出題 F題 小苯的因子查詢 思路 考慮求因子個數&#xff0c;用質因數分解&#xff1b;奇數因子只需要去掉質數為2的情況&#xff0c;用除法。 這里有個比較妙的細節是&#xff0c;提前處理出數字x的最小質因數&#xff0…

使用直覺理解不等式

問題是這個&#xff1a; 題目 探究 ∣ max ? b { q 1 ( z , b ) } ? max ? b { q 2 ( z , b ) } ∣ ≤ max ? b ∣ q 1 ( z , b ) ? q 2 ( z , b ) ∣ |\max_b\{q_1(z,b)\}-\max_b\{q_2(z,b)\}|\le\max_b|q_1(z,b)-q_2(z,b)| ∣maxb?{q1?(z,b)}?maxb?{q2?(z,b)}∣≤…

惡心的win11更新DIY 設置win11更新為100年

?打開注冊表編輯器?&#xff1a;按下Win R鍵&#xff0c;輸入regedit&#xff0c;然后按回車打開注冊表編輯器。?12?導航到指定路徑?&#xff1a;在注冊表編輯器中&#xff0c;依次展開HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings?新建DWORD值?&…

嵌入式驅動學習

時鐘 定義 周期型的0、1信號 時鐘信號由“心臟”時鐘源產生&#xff0c;通過“動脈”時鐘樹傳播到整個芯片中。 SYSCLK系統時鐘&#xff0c;由HSI、HSE、PLLCLK三選一。 HCLK是AHB總線時鐘&#xff0c; PCLK是APB總線時鐘。 使用某個外設&#xff0c;必須要先使能該外設時鐘系統…

Java:從入門到精通,你的編程之旅

Java&#xff0c;一門歷久彌新的編程語言&#xff0c;自誕生以來就以其跨平臺性、面向對象、穩定性和安全性等特性&#xff0c;在企業級應用開發領域占據著舉足輕重的地位。無論你是初學者還是經驗豐富的開發者&#xff0c;Java 都能為你提供強大的工具和廣闊的舞臺。 為什么選…

Linux:深入理解數據鏈路層

實際上一臺主機中&#xff0c;報文并沒有通過網絡層直接發送出去&#xff0c;而是交給了自己的下一層協議——數據鏈路層&#xff01;&#xff01; 一、理解數據鏈路層 網絡層交付給鏈路層之前&#xff0c;會先做決策再行動&#xff08;會先查一下路由表&#xff0c;看看目標網…

Python基本語法(類和實例)

類和實例 類和對象是面向對象編程的兩個主要方面。類創建一個新類型&#xff0c;而對象是這個 類的實例&#xff0c;類使用class關鍵字創建。類的域和方法被列在一個縮進塊中&#xff0c;一般函數 也可以被叫作方法。 &#xff08;1&#xff09;類的變量&#xff1a;甴一個類…

2025 年如何使用 Pycharm、Vscode 進行樹莓派 Respberry Pi Pico 編程開發詳細教程(更新中)

micropython 概述 micropython 官方網站&#xff1a;https://www.micropython.org/ 安裝 Micropython 支持固件 樹莓派 Pico 安裝 Micropython 支持固件 下載地址&#xff1a;https://www.raspberrypi.com/documentation/microcontrollers/ 選擇 MicroPython 下載 RPI_PIC…

flink rocksdb狀態說明

文章目錄 1.默認情況2.flink中的狀態3.RocksDB4.對比情況5.使用6.RocksDB架構7.參考文章8.總結提示:以下主要考慮flink 狀態永久存儲 rocksdb情況,做一些簡單說明 1.默認情況 當flink使用rocksdb存儲狀態時。無論是永久存儲還是臨時存儲都可能會落盤寫文件(如果沒有配置存儲…

安裝SDL和FFmpeg

1、先記錄SDL 這玩意還是有一點講究的 具體步驟&#xff1a; 下載 SDL包&#xff1a; 鏈接&#xff1a;https://www.libsdl.org/release/SDL2-2.0.14.tar.gz 可以用迅雷&#xff0c;下載完之后&#xff0c; 解壓&#xff1a; tar -zxvf SDL2-2.0.14.tar.gz進入安裝目錄 cd …

2022年408真題及答案

2022年計算機408真題 2022年計算機408答案 2022 408真題下載鏈接 2022 408答案下載鏈接

Spring AI聊天模型API:輕松構建智能聊天交互

Spring AI聊天模型API&#xff1a;輕松構建智能聊天交互 前言 在當今數字化時代&#xff0c;智能聊天功能已成為眾多應用程序提升用戶體驗、增強交互性的關鍵要素。Spring AI的聊天模型API為開發者提供了一條便捷通道&#xff0c;能夠將強大的AI驅動的聊天完成功能無縫集成到…

Softmax回歸與單層感知機對比

(1) 輸出形式 Softmax回歸 輸出是一個概率分布&#xff0c;通過Softmax函數將線性得分轉換為概率&#xff1a; 其中 KK 是類別數&#xff0c;模型同時計算所有類別的概率。 單層感知機 輸出是二分類的硬決策&#xff08;如0/1或1&#xff09;&#xff1a; 無概率解釋&#x…

【React】Hooks 解鎖外部狀態安全訂閱 useSyncExternalStore 應用與最佳實踐

一、背景 useSyncExternalStore 是 React 18 引入的一個 Hook&#xff1b;用于從外部存儲&#xff08;例如狀態管理庫、瀏覽器 API 等&#xff09;獲取狀態并在組件中同步顯示。這對于需要跟蹤外部狀態的應用非常有用。 二、場景 訂閱外部 store 例如(redux,mobx,Zustand,jo…

Dify框架面試內容整理-如何評估基于Dify開發的AI應用的效果?

評估基于 Dify 開發的 AI 應用效果,需要從 用戶體驗、技術性能 與 業務價值 三個層面綜合衡量。以下是詳細的評估框架,涵蓋三個關鍵點: 用戶反饋與滿意度

Linux 系統下VS Code python環境配置!

Anaconda安裝&#xff1a; 在 Linux 系統中安裝下載好的 Anaconda3-2024.10-1-Linux-x86_64.sh&#xff0c;可按以下步驟操作&#xff1a; 1. 賦予安裝腳本執行權限 打開終端&#xff0c;切換到安裝包所在目錄&#xff08;假設在 software 文件夾中&#xff09;&#xff0c;…

項目實戰-基于信號處理與SVM機器學習的聲音情感識別系統

目錄 一.背景描述 二.理論部分 三.程序設計 編程思路 流程圖 1.信號部分 創建數據 generate_samples.py 頭文件 生成函數 generate_emotion_sample 傳入參數 存儲路徑 生成參數 創建基礎正弦波信號 調制基礎正弦波 對于憤怒可以增加噪聲 歸一化信號 存儲 主函…