多態以及多態底層的實現原理

本章目標

1.多態的概念
2.多態的定義實現
3.虛函數
4.多態的原理

1.多態的概念

多態作為面對三大特性之一,它所指代的和它的名字一樣,多種形態.但是這個多種形態更多的指代是函數的多種形態.
多態分為靜態多態和動態多態.
靜態多態在前面已經學習過了,就是函數重載以及模板,它們是在編譯時就已經確定下來了,也被成為編譯時多態.它們通過傳不同的參數實現函數不同的形態.
我們在這里主要將動態多態,也就是運行時多態.當我們運行某個函數的時候,它會根據傳過來的對象的不同,來實現不同的行為,簡單來說就是統一繼承體系下的不同類對象去調用同一個函數產生了不同的行為

2.多態的定義實現

2.1實現多態的條件

1.必須是基類的指針或者引用去調用虛函數
2.虛函數必須完成了重寫或者覆蓋
因為我們前面所將的切片的類型兼容轉換,只有基類的指針或者引用才能即指向基類對象又指向派生類對象.
虛函數的重寫或者覆蓋所指的是它的實現重寫,這樣基類和派生類才能有不同的函數.
才能實現多態.

#include<iostream>
using namespace std;
class A
{
public:virtual void  a(){cout << "A" << endl;}
};
class b :public A
{
public:virtual void a(){cout << "b" << endl;}};
int main()
{A* ptr1 = new A;A* ptr2 = new b;ptr1->a();ptr2->a();return 0;
}

在這里插入圖片描述

以上就是多態的實現.

3.虛函數

類成員函數,在函數的前面加上virtual修飾,我們就稱之為虛函數.非類成員函數是不能用virtual修飾的.

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

3.1虛函數的重寫覆蓋

虛函數的重寫覆蓋所指的是在派生類之中有一個和基類完全的一樣的虛函數(返回值,函數名,參數列表),那么就叫做虛函數的重寫覆蓋.
在有的地方只在基類的虛函數的地方加上virtual,而在派生類中,并沒有加入virtual來進行修飾,這樣也是構成重寫或者覆蓋的.因為從基類繼承下來的虛函數,在派生類也繼承下來了它的虛函數屬性

3.2協變

在派生類重寫基類虛函數的時候,我們可以讓派生的返回類型與基類不同,去返回基類或者派生類的指針或者引用,這個指針或者引用可以是其他類的.

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;}};void Func(Person* ptr){ptr->BuyTicket();}int main(){Person ps;Student st;Func(&ps);Func(&st);return 0;}

3.3析構函數的重寫

只要基類的析構函數為虛函數,它的派生類的析構一定會與基類的析構函數構成重寫,在前面我們說繼承的時候講到析構函數會在編譯時統一將名稱處理成destructor,這樣它們就構成了隱藏,而在這里則是構成了重寫.

#include<iostream>
using namespace std;
class A
{
public:virtual void  a(){cout << "A" << endl;}virtual ~A(){cout << "~A" << endl;}
};
class b :public A
{
public:virtual void a(){cout << "b" << endl;}~b(){delete[] arr;cout << "~b" << endl;}
private:int* arr = new int[10];
};
int main()
{A* ptr1 = new A;A* ptr2 = new b;ptr1->a();ptr2->a();delete ptr1;delete ptr2;return 0;
}

在這里插入圖片描述

3.4override和final關鍵字

從上面我們可以看出c++對虛函數的要求比較嚴格,可能有的時候參數類型寫錯了導致無法構成重寫.我們就可以override來幫助我們進行檢查.
在這里插入圖片描述

class D
{
public:virtual void  d(){cout << "dadad" << endl;}
};
class E:public D
{
public:virtual void d(int a) override{cout << "dada" << endl;}
};

final關鍵字我們已經見過了,我們在實現一個不能被繼承的類的時候,我們用final修飾或者構造私有.
而在這里我們不想讓虛函數被繼承也可用final來進行修飾.
在這里插入圖片描述

3.5重載/重寫/隱藏對比

重載
1.在統一作用域
2.函數名相同,參數不同,返回值可相同,可不同
重寫
1.在統一繼承體系下的不同的基類和派生類的作用域之中.
2.函數名,參數,返回值都必須相同,協變例外
3.兩個函數都必須時虛函數
隱藏
1.在統一繼承體系下的不同的基類和派生類的作用域之中.
2.函數名相同
3.兩個函數只要不是重寫就是隱藏.
4.變量名相同也可以構成隱藏

隱藏和重寫的二者上是有所重疊但是并不完全相同

3.6純虛函數與抽象類

在虛函數的后面加上=0,這個虛函數就是純虛函數,純虛函數所在的類被稱為抽象類,抽象類是不能夠實例化對象的,并且抽象類被繼承之后的派生類的虛函數一定要被重寫.
否則這個類也是抽象類.

class F
{
public:virtual void ff() = 0;};
class G :public F
{virtual void ff(){cout << "dada" << endl;}
};

在這里插入圖片描述

4.多態的原理

class Base{public:virtual void Func1(){cout << "Func1()" << endl;}protected:int _b = 1;char _ch = 'x';};

當我們去算上面的類的時候,我們正常的結果是8bytes.
實際上則不同
在這里插入圖片描述
它的大小是12bytes.
在這里插入圖片描述
當我們創建一個Base類的對象來看的時候,我們發現除了上面的我們類中創建兩個成員變量還有一個vfptr的函數指針.在x86的環境下它的大小就是12bytes.
這個指針就是虛函數表指針,每一個含有虛函數的類中,至少含有一個虛函數表,這個表里面放在虛函數的地址
在這里插入圖片描述
從底層的角度我們該如何看到a是如何被調用的呢,當父類指針ptr1指向A的時候調用A的a函數,ptr2指向b的時候調用b中的a函數呢.
實際上當調用虛函數的時候,去調用函數的地址的時候,不是編譯時通過對象來確定虛函數的地址.而是通過對象中的虛表來去call這個虛函數的地址

class Person {public:virtual void BuyTicket() { cout << "買票全價" << endl; }private:   
string _name;};class Student : public Person {public:virtual void BuyTicket() { cout << "買票打折" << endl; }private:   
string _id;};class Soldier: public Person {public:virtual void BuyTicket() { cout << "買票優先" << endl; }private:   
string _codename;};void Func(Person* ptr){// 這?可以看到雖然都是Person指針Ptr在調?BuyTicket 
// 但是跟ptr沒關系,?是由ptr指向的對象決定的。ptr->BuyTicket();}int main(){// 其次多態不僅僅發?在派?類對象之間,多個派?類繼承基類,重寫虛函數后// 多態也會發?在多個派?類之間。Person ps;Student st;Soldier sr;Func(&ps);Func(&st);Func(&sr);return 0;}

4.1動態綁定與靜態綁定

對于通過動態多態(父類的指針或者引用)去調用的函數,也就是運行時到指定對象的虛函數表中去調用函數的,我們叫做動態綁定.
對不滿足動態多態條件的在編譯時確定函數地址或者通過對象去確定函數的地址的,我們叫做靜態綁定

4.2虛函數表

1.基類的虛函數表中存放著所以基類虛函數的地址,同一類型的對象公用同一張虛表,不同類的虛表之間時獨立的.基類和派生類的虛表時相互獨立的
2.派?類由兩部分構成,繼承下來的基類和??的成員,?般情況下,繼承下來的基類中有虛函數表指針,??就不會再?成虛函數表指針。但是要注意的這?繼承下來的基類部分虛函數表指針和基類對象的虛函數表指針不是同?個,就像基類對象的成員和派?類對象中的基類對象成員也獨?的。
3.派?類中重寫的基類的虛函數,派?類的虛函數表中對應的虛函數就會被覆蓋成派?類重寫的虛函數地址。
4.派?類的虛函數表中包含,(1)基類的虛函數地址,(2)派?類重寫的虛函數地址完成覆蓋,派?類??的虛函數地址三個部分。
5.虛函數表本質是?個存虛函數指針的指針數組,?般情況這個數組最后?放了?個0x00000000標記。(這個C++并沒有進?規定,各個編譯器??定義的,vs系列編譯器會再后?放個0x00000000標記,g++系列編譯不會放)
6.虛函數存在哪的?虛函數和普通函數?樣的,編譯好后是?段指令,都是存在代碼段的,只是虛函數的地址?存到了虛表中。
7.虛函數表存在哪的?這個問題嚴格說并沒有標準答案C++標準并沒有規定,我們寫下?的代碼可以對?驗證?下。vs下是存在代碼段(常量區)

class Base {public:virtual void func1() { cout << "Base::func1" << endl; }virtual void func2() { cout << "Base::func2" << endl; }void func5() { cout << "Base::func5" << endl; }protected:int a = 1;};class Derive : public Base{public:// 重寫基類的func1 
virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func1" << endl; }
int main(){int i = 0;static int j = 1;int* p1 = new int;const char* p2 = "xxxxxxxx";printf("棧:%p\n", &i);printf("靜態區:%p\n", &j);printf("堆:%p\n", p1);printf("常量區:%p\n", p2);Base b;Derive d;Base* p3 = &b;Derive* p4 = &d;printf("Person虛表地址:%p\n", *(int*)p3);printf("Student虛表地址:%p\n", *(int*)p4);printf("虛函數地址:%p\n", &Base::func1);printf("普通函數地址:%p\n", &Base::func5);return 0;}

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

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

相關文章

linux下開發NFC讀寫器

linux下使用NFC讀卡器&#xff0c;基于QT5開發 創建工程&#xff0c;引入lib開始編寫代碼 創建工程&#xff0c;引入lib 創建一個QT工程&#xff0c;如果是控制臺程序&#xff0c;則去掉gui QT - gui引入lib庫 LIBS -L$$PWD/lib -lyw60x這里需要將libyw60x.so庫文件放在工程…

Linux基礎使用-筆記

1. 文件和目錄操作 查看當前目錄&#xff1a;pwd 命令用于顯示當前工作目錄的完整路徑。 pwd切換目錄&#xff1a;cd 命令用于切換工作目錄。 # 切換到指定目錄 cd /home/user/Documents # 切換到上一級目錄 cd .. # 切換到用戶主目錄 cd ~列出目錄內容&#xff1a;ls 命令用…

DAG(有向無環圖)計算模型面試內容整理-拓撲排序(Topological Sort)和節點依賴與并行度

拓撲排序(Topological Sort) 拓撲排序(Topological Sort): 拓撲排序是針對有向無環圖(DAG)的一種線性排序方法。這種排序方法的特點是,對于DAG中的每一條有向邊 (A → B),在拓撲排序中節點A總是排在節點B之前。

23種設計模式-結構型模式之享元模式(Java版本)

Java 享元模式&#xff08;Flyweight Pattern&#xff09;詳解 &#x1f98b; 什么是享元模式&#xff1f; 享元模式是一種結構型模式&#xff0c;它通過共享相同的對象來減少內存消耗&#xff0c;適用于大量細粒度對象的場景。關鍵思想是緩存重復出現的對象&#xff0c;避免…

瀏覽器訪問背后的秘密:從加載到關閉,數據是否會丟失?

? 一次瀏覽器訪問 www.xxx.com 背后發生了什么&#xff1f; —— 以及“我點了 &#xff0c;數據會不會丟&#xff1f;”的深度剖析 適讀人群&#xff1a;Web 開發者、運維工程師、性能調優/安全從業者 1?? 打開瀏覽器敲下網址&#xff1a;鏈路是如何啟動的&#xff1f; 階…

【HDFS入門】深入解析DistCp:Hadoop分布式拷貝工具的原理與實踐

目錄 1 DistCp概述與應用場景 2 DistCp架構設計解析 2.1 系統架構圖 2.2 執行流程圖 3 DistCp核心技術原理 3.1 并行拷貝機制 3.2 斷點續傳實現原理 4 DistCp實戰指南 4.1 常用命令示例 4.2 性能優化策略 5 異常處理與監控 5.1 常見錯誤處理流程 5.2 監控指標建議…

hbuilderx云打包生成的ipa文件如何上架

使用hbuilderx打包&#xff0c;會遇到一個問題。開發的ios應用&#xff0c;需要上架到app store&#xff0c;因此&#xff0c;就需要APP store的簽名證書&#xff0c;并且還需要一個像xcode那樣的工具來上架app store。 我們這篇文章說明下&#xff0c;如何在windows電腦&…

第十五屆藍橋杯 2024 C/C++組 拼正方形

目錄 題目&#xff1a; 題目描述&#xff1a; 題目鏈接&#xff1a; 思路&#xff1a; 思路詳解&#xff1a; 易錯點&#xff1a; 代碼&#xff1a; 代碼詳解&#xff1a; 題目&#xff1a; 題目描述&#xff1a; 題目鏈接&#xff1a; P10898 [藍橋杯 2024 省 C] 拼正…

華為云獲取IAM用戶Token的方式及適用分析

&#x1f9e0; 一、為什么要獲取 IAM 用戶 Token&#xff1f; 我們用一個生活中的比喻來解釋&#x1f447;&#xff1a; &#x1f3e2; 比喻場景&#xff1a; 你要去一個 高級寫字樓&#xff08;華為云物聯網平臺&#xff09; 辦事&#xff08;調用接口管理設備&#xff09;&…

樂聚機器人與地瓜機器人達成戰略合作,聯合發布Aelos Embodied具身智能

要聞 4月19日&#xff0c;在CCF人形機器人與人工智能技術巡回研討會&#xff08;武漢站&#xff09;上&#xff0c;樂聚機器人與地瓜機器人達成戰略合作&#xff0c;雙方將基于RDK X5、RDK S100以及更高性能的國產大算力平臺&#xff0c;就夸父&#xff08;KUAVO&#xff09;、…

Web3架構下的數據隱私與保護

在這個信息爆炸的時代&#xff0c;Web3的概念如同一股清流&#xff0c;以其去中心化的特性&#xff0c;為數據隱私與保護帶來了新的希望。Web3&#xff0c;也被稱作下一代互聯網&#xff0c;它通過區塊鏈技術實現數據的去中心化存儲和處理&#xff0c;旨在提高數據的安全性和隱…

【OceanBase相關】02-OceanBase數據庫NFS備份實踐

文章目錄 一、前言1、概述2、備份方式3、備份流程4、恢復流程二、NFS備份1、注意事項2、服務端配置3、客戶端配置4、備份策略配置三、常用操作四、Q&A1、數據備份任務執行失敗,提示`start log archive backup when not STOP is not supported`1.1、問題說明1.2、解決措施2…

一行命令打開iOS模擬器

要在 Mac 命令行打開 iPhone 15 Pro 模擬器&#xff0c;需滿足已安裝 Xcode 這一前提條件&#xff0c;以下是具體操作步驟&#xff1a; 步驟一&#xff1a;列出所有可用模擬器設備 打開終端&#xff08;Terminal&#xff09;&#xff0c;輸入并執行以下命令&#xff0c;用于列…

Java虛擬機(JVM)家族發展史及版本對比

Java虛擬機&#xff08;JVM&#xff09;家族發展史及版本對比 一、JVM家族發展史 1. 早期階段&#xff08;1996-2000&#xff09; Classic VM&#xff08;Java 1.0-1.1&#xff09;&#xff1a; 廠商&#xff1a;Sun Microsystems&#xff08;Oracle前身&#xff09;。特點&…

嘻游電玩三端客戶端部署實戰:PC + Android + iOS 環境全覆蓋教程

本篇文章將針對“網狐系列嘻游電玩組件”的三端客戶端&#xff08;PC端、安卓端、iOS端&#xff09;進行詳細部署實操講解。文章將以實測部署為核心&#xff0c;提供資源結構說明、平臺適配調整、打包配置、常見問題修復&#xff0c;并輔以必要的關鍵配置代碼。 一、客戶端資源…

LabVIEW實現Voronoi圖繪制功能

該 LabVIEW 虛擬儀器&#xff08;VI&#xff09;借助 MathScript 節點&#xff0c;實現基于手機信號塔位置計算 Voronoi 圖的功能。通過操作演示&#xff0c;能直觀展示 Voronoi 圖在空間劃分上的應用。 各部分功能詳細說明 隨機地形創建部分 功能&#xff1a;根據 “Maximum a…

web刷題筆記

2024isctf ezrce 禁用了一些關鍵字符&#xff0c;查詢函數&#xff0c;系統執行函數&#xff0c;執行函數都有&#xff0c;空格也和斜桿也禁用了&#xff0c;但是其他一些很大一部分字符都沒有禁用&#xff0c;屬于關鍵詞禁用的類型&#xff0c;正常的步驟是去查一下列表&#…

集結號海螺捕魚游戲源碼解析(第二篇):水滸傳捕魚模塊邏輯與服務器幀同步詳解

本篇將全面解構“水滸傳”子游戲的服務端核心邏輯、幀同步機制、魚群刷新規則、客戶端命中表現與服務器計算之間的協同方式&#xff0c;聚焦于 C 與 Unity3D 跨端同步的真實實現過程。 一、水滸傳捕魚模塊資源結構 該模塊包含三部分核心目錄&#xff1a; 子游戲/game_shuihuz…

【產品經理從0到1】原型及Axure介紹

原型分類 原型的三種分類&#xff1a; 草圖原型&#xff1a;?繪稿&#xff0c;制作?便&#xff0c;修改不?便&#xff1b;低保真原型&#xff1a;簡單交互&#xff0c;?設計圖&#xff1b; 最好的原型是??灰的&#xff1b;?保真原型&#xff1a;復雜交互&#xff0c;有…

CVE-2024-23897-Jenkins 2.441之前版本存在任意文件讀取漏洞

1.漏洞介紹 Jenkins 2.441及更早版本&#xff0c;以及LTS 2.426.2及更早版本沒有禁用其CLI命令解析器的一個功能&#xff0c;該功能會將參數中字符后跟的文件路徑替換為該文件的內容&#xff0c;允許未經身份驗證的攻擊者讀取Jenkins控制器文件系統上的任意文件。 2.poc利用 下…