多態——細致講解

🔶多態基礎概念
?🔶概念
??🔱多態性
??🔱多態——重新(覆蓋)
?🔶示例
??🔶基本使用方法
??🔶特例
???🔱協變
???🔱析構函數重寫
?🔱多態原理
??🔱1. 虛函數形成虛表
??🔱2. 虛函數存儲位置(覆蓋)
??🔱3. 多態中重寫的虛函數存儲位置
???🔱1. 重寫原理——虛表
???🔱2. 單繼承中,子類新增虛函數會存到父類的虛表中——普通繼承
???🔱3. 單繼承中,子類新增虛函數會存到父類的虛表中——虛繼承
???🔱4. 同類公用一個虛表;父類和子類不共用一張虛表
?🔱多態例題
🔱經典問題

多態基礎概念

概念

多態性

?1. 靜態多態:函數重載和運算符重載
?2. 動態多態:繼承和虛函數

多態——重寫(覆蓋)

?1. 父類的指針/引用調用虛函數
?2. 調用的虛函數必須是子類重寫的虛函數
這樣就能在指針調用相應的對象函數的時候使用相應的成員函數,具體看示例
這里條件很嚴格
重寫的函數要是一摸一樣——返回值,函數名,參數個數,參數位置,參數類型都要完全一樣,虛函數之后的const也要一樣

示例

基本使用方法
  1. 父類中需要使用virtual修飾函數,子類中virtual可以不寫
class A
{
public:virtual void func(){puts("A-->func");}
};
class B:public A
{
public:virtual void func(){puts("B-->func");}
};
int main()
{// 父類指向子類A* a1 = new B;a1->func();// 父類指向父類a1 = new A;a1->func();// 父類引用子類B tb;A& a2 = tb;a2.func();// 父類引用父類A ta;A& a3 = ta; // 不能直接使用a2=ta,引用不能重新賦值,雖然他不會報錯,但是他的結果是錯的a3.func();return 0;
}

在這里插入圖片描述


  1. final 修飾類——不能繼承

在這里插入圖片描述

修飾虛函數——不能背重寫

在這里插入圖片描述

  1. override ——這個函數一定要重新父類的某一個虛函數

在這里插入圖片描述

一定要注意這兩個關鍵字加載虛函數結尾

特例
協變

虛函數的返回值可以不一樣,只能出現父類返回父類的指針/引用,子類返回子類的指針/引用

在這里插入圖片描述

不可以一個返回指針,一個返回引用
只能同時返回指針/同時返回引用

析構函數重寫
#include<iostream>
using namespace std;
typedef void (*T)();
class A
{
public:virtual void fun1(){cout << "A::fun1" << endl;}virtual void fun2(){cout << "A::fun2" << endl;}~A(){cout << "delete A" << endl;}int _a = 1;};
class B :public A
{
public:virtual void fun1(){cout << "B::fun1" << endl;}virtual void fun3(){cout << "B::fun3" << endl;}~B(){cout << "delete B" << endl;}int _b = 2;
};
int main()
{A* a = new B;delete a;return 0;
}

在這里插入圖片描述

delete釋放看的是類型,也就是說這里delete調用的是A的析構函數
根本上說,delete會被處理成—> destructor() + operator delete,所以他們能構成重寫,在具體實現的時候需要寫成virtual

	virtual ~A(){cout << "delete A" << endl;}

在這里插入圖片描述


?多態原理

?1. 虛函數形成虛表
#include<iostream>
using namespace std;
typedef void (*T)();
class A
{
public:virtual void fun1(){cout << "A::fun1" << endl;}virtual void fun2(){cout << "A::fun2" << endl;}};
int main()
{A a;return 0;
}

在這里插入圖片描述

?2. 虛函數存儲位置

虛函數和普通函數放在一起,虛表存儲在代碼段

?3. 多態中重寫的虛函數存儲位置
🎭1. 重寫原理——虛表
#include<iostream>
using namespace std;
typedef void (*T)();
class A
{
public:virtual void fun1(){cout << "A::fun1" << endl;}virtual void fun2(){cout << "A::fun2" << endl;}};
class B :public A
{
public:virtual void fun1(){cout << "B::fun1" << endl;}
};
int main()
{B b;return 0;
}

在這里插入圖片描述

🎭2. 單繼承中,子類新增虛函數會存到父類的虛表中——普通繼承
#include<iostream>
using namespace std;
typedef void (*T)();
class A
{
public:virtual void fun1(){cout << "A::fun1" << endl;}virtual void fun2(){cout << "A::fun2" << endl;}};
class B :public A
{
public:virtual void fun1(){cout << "B::fun1" << endl;}virtual void fun3(){cout << "B::fun3" << endl;}
};
int main()
{B b;print((T*)(*(int*)(&b)));return 0;
}

在這里插入圖片描述
vs中虛表通常在最后一個都是0,Linux不是

在這里插入圖片描述

🎭3. 單繼承中,子類新增虛函數會存到父類的虛表中——虛繼承
#include<iostream>
using namespace std;
typedef void (*T)();
class A
{
public:virtual void fun1(){cout << "A::fun1" << endl;}virtual void fun2(){cout << "A::fun2" << endl;}int _a = 1;};
class B :virtual public A
{
public:virtual void fun1(){cout << "B::fun1" << endl;}virtual void fun3(){cout << "B::fun3" << endl;}int _b = 2;
};
int main()
{B b;A* a = &b; // 要注意這種寫法,確保他能準確跳到下一個虛表處print((T*)(*(int*)(a)));print((T*)(*(int*)(&b)));return 0;
}

在這里插入圖片描述
在這里插入圖片描述

🎭4. 同類公用一個虛表;父類和子類不共用一張虛表
#include<iostream>
using namespace std;
typedef void (*T)();
class A
{
public:virtual void fun1(){cout << "A::fun1" << endl;}virtual void fun2(){cout << "A::fun2" << endl;}int _a = 1;};
class B :virtual public A
{
public:virtual void fun1(){cout << "B::fun1" << endl;}int _b = 2;
};
int main()
{A a;B b1;B b2;return 0;
}

在這里插入圖片描述


🖼多態例題

class A
{
public:virtual void fun(int val = 1){cout << "val = " << val << endl;}virtual void test(){fun();}
};
class B :public A
{
public:virtual void fun(int val = 0){cout << "val = " << val << endl;}
};
int main()
{A* a = new B;a->test();return 0;
}
void print(T a[])
{for (int i = 0; a[i] != 0; i++){printf("[%d]--->%p\n", i, a[i]);}puts("");
}
int main()
{B b;print((T*)(*(int*)(&b)));return 0;
}

在這里插入圖片描述

  1. 父類指向子類,調用的test函數,test函數是父類的虛函數,類內的函數有一個默認的this指針,test內部調用的fun函數實際上是this->fun(this類型是A*——父類的指針指向虛函數),fun是子類重寫的虛函數(函數是子類重寫的虛函數)——滿足多態條件
  2. 虛函數中的this是根據是否重寫確定的,這里的test沒有被重寫,是A*this指針,然后調用fun,fun是經過重寫的函數,所以調用的是重寫的函數
  3. 虛函數繼承的是函數的接口,重寫的是函數的實現

所以缺省值才是1


#include<iostream>
using namespace std;class A
{
public:virtual void fun(int val = 0){printf("A::fun()--> %d", val);}virtual void test(){fun();}
};
class B:public A
{
public:void fun(int val = 1){printf("B::fun()--> %d", val);}
};
int main()
{B b;b.test();return 0;
}

在這里插入圖片描述


🔒經典問題

  1. 什么是多態?
  2. 什么是重載、重寫(覆蓋)、重定義(隱藏)?
  3. 多態的實現原理?
  4. inline函數可以是虛函數嗎?

可以,不構成多態就是inline,構成多態就不是inline

  1. 靜態成員可以是虛函數嗎?

不能,因為靜態成員函數沒有this指針,使用類型::成員函數
的調用方式無法訪問虛函數表,所以靜態成員函數無法放進虛函數表。

  1. 構造函數可以是虛函數嗎?

不能,虛表在編譯時生成
在調用構造函數之后,但是虛表指針在成員初始化之前

  1. 析構函數可以是虛函數嗎?

本就應該是,在A* a = new B;這種場景下,在釋放子類對象時,需要將析構函數變成虛函數

  1. 對象訪問普通函數快還是虛函數更快?

首先如果是普通對象,是一樣快的。如果構成多態,就是普通函數快,因為運行時調用虛函數需要到虛函數表中去查找。

  1. 虛函數表是在什么階段生成的,存在哪的?

虛函數表是在編譯階段就生成的,一般情況下存在代碼段(常量區)的。

  1. C++菱形繼承的問題?虛繼承的原理?

菱形繼承會造成祖宗類數據冗余的問題
在每一個繼承自祖宗類的派生類中,使用一個指針指向一個偏移量,根據偏移量找到的地址就是祖宗類的數據,并且這個數據只有一份

  1. 什么是抽象類?抽象類的作用?

抽象類含有形如 virtual void fun() =0; 的基類/派生類
強制派生類重寫父類的實現


原理的角度理解,重寫之后將fun虛函數進行覆蓋test是A*this調用經過重寫的虛函數fun符合多態的條件,并且繼承的是接口不是實現fun虛函數繼承父類函數接口,并使用重寫的虛函數實現,最終形成了這個樣子


優秀多態文章
優秀多態文章
為什么要使用父類指針和引用實現多態,而不能使用對象?
虛析構函數
虛表位置
虛表位置

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

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

相關文章

`useState` 和 `useImmer` 都是 React 中用于管理狀態的鉤子

useImmer如何使用&#xff1a; 安裝&#xff1a;yarn add use-immer使用&#xff1a; const [data, updateData] useImmer({fields: [], });updateData((draft) > {draft.fields.splice(index, 1, {id:1});});useState 和 useImmer 都是 React 中用于管理狀態的鉤子&…

redis實戰筆記匯總

文章目錄 1 NoSQL入門概述1.1 能干嘛&#xff1f;1.2 傳統RDBMS VS NOSQL1.3 NoSQL數據庫的四大分類1.4 分布式數據庫CAP原理 BASE原則1.5 分布式集群簡介1.6 淘寶商品信息的存儲方案 2 Redis入門概述2.1 是什么&#xff1f;2.2 能干嘛&#xff1f;2.3 怎么玩&#xff1f;核心…

46、WEB攻防——通用漏洞PHP反序列化原生類漏洞繞過公私有屬性

文章目錄 幾種常用的魔術方法1、__destruct()2、__tostring()3、__call()4、__get()5、__set()6、__sleep()7、__wakeup()8、__isset()9、__unset()9、__invoke() 三種變量屬性極客2019 PHPphp原生類 幾種常用的魔術方法 1、__destruct() 當刪除一個對象或對象操作終止時被調…

關于 yarn 的中央倉庫 registry.yarnpkg.com

"Yarn" 是一個開源的 JavaScript 包管理工具&#xff0c;用于管理項目中的依賴關系。Yarn 通過一個叫做 "registry" 的中央倉庫來存儲和檢索各種 JavaScript 包。這個中央倉庫可以通過 https://registry.yarnpkg.com/ 訪問&#xff0c;它是 Yarn 包管理系統…

像用Excel一樣用Python:pandasGUI

文章目錄 啟動數據導入繪圖 啟動 眾所周知&#xff0c;pandas是Python中著名的數據挖掘模塊&#xff0c;以處理表格數據著稱&#xff0c;并且具備一定的可視化能力。而pandasGUI則為pandas打造了一個友好的交互窗口&#xff0c;有了這個&#xff0c;就可以像使用Excel一樣使用…

數據庫運維01

數據備份多重方案 核心sql語句 mysql復制架構 mysql 生產實踐 mysql可用的集群和中間件 linux環境 linux的命令要掌握 dba數據庫管理員 it部門負責數據庫維護 一定規模的企業 健康良好的運行數據庫 對數據庫做策略&#xff0c;保證數據庫的穩定 查數據要盡快的返回 復雜的數據需…

【Spring Boot 3】的安全防線:整合 【Spring Security 6】

簡介 Spring Security 是 Spring 家族中的一個安全管理框架。相比與另外一個安全框架Shiro&#xff0c;它提供了更豐富的功能&#xff0c;社區資源也比Shiro豐富。 一般來說中大型的項目都是使用SpringSecurity 來做安全框架。小項目有Shiro的比較多&#xff0c;因為相比與Sp…

Linux線程【互斥與同步】

目錄 1.資源共享問題 1.1多線程并發訪問 1.2臨界區和臨界資源 1.3互斥鎖 2.多線程搶票 2.1并發搶票 2.2 引發問題 3.線程互斥 3.1互斥鎖相關操作 3.1.1互斥鎖創建與銷毀 3.1.2、加鎖操作 3.1.3 解鎖操作 3.2.解決搶票問題 3.2.1互斥鎖細節 3.3互斥…

github用法詳解

本文是一篇面向全體小白的文章,圖文兼備。為了讓小白們知道如何使用GitHub,我努力將本文寫得通俗易懂,盡量讓剛剛上網的小白也能明白。所以各位程序員們都可以滑走了~ 啥是GitHub? 百度百科會告訴你, GitHub是一個面向開源及私有軟件項目的托管平臺,因為只支持Git作為…

大模型訓練——PEFT與LORA介紹

大模型訓練中的PEFT&#xff08;Parameter-Efficient Fine-Tuning&#xff09;與LoRA&#xff08;Low-Rank Adaptation&#xff09;是兩種重要的技術&#xff0c;它們在大型預訓練模型的應用中發揮著重要作用。 首先&#xff0c;讓我們來了解一下PEFT。PEFT是一種參數高效的微…

GO基本類型

Go語言同時提供了有符號和無符號的整數類型。 有符號整型&#xff1a;int、int8、int64、int32、int64無符號整型&#xff1a;uint、uint8、uint64、uint32、uint64、uintptr 有符號整型范圍&#xff1a;-2^(n-1) 到 2^(n-1)-1 無符號整型范圍: 0 到 2^n-1 實際開發中由于編…

英語中的提問方式(問法)(bug提問、bug描述)

文章目錄 英語提問方式一、單詞、短語、句子的意思1.1 提問單詞的意思1.2 提問短語的意思1.3 提問句子的意思 二、在編程中提問2.1 提問bug2.2 請求代碼幫助 如何提出反問句1. 構建反問句的基本結構2. 提問反問句的方法3. 理解反問句的意圖 在口語中提問&#xff1a;確保清晰度…

Topaz Gigapixel AI:讓每一張照片都煥發新生mac/win版

Topaz Gigapixel AI 是一款革命性的圖像增強軟件&#xff0c;它利用先進的人工智能技術&#xff0c;能夠顯著提升圖像的分辨率和質量。無論是攝影愛好者還是專業攝影師&#xff0c;這款軟件都能幫助他們將模糊的、低分辨率的照片轉化為清晰、細膩的高分辨率圖像。 Topaz Gigap…

JavaWeb——011 SpringBootWeb綜合案例(刪除/修改員工、文件上傳、配置文件)

SpringBootWeb案例 目錄 SpringBootWeb案例1. 新增員工1.1 需求1.2 接口文檔1.3 思路分析1.4 功能開發1.5 功能測試1.6 前后端聯調 2. 文件上傳2.1 簡介2.2 本地存儲2.3 阿里云OSS2.3.1 準備2.3.2 入門2.3.3 集成 3. 修改員工3.1 查詢回顯3.1.1 接口文檔3.1.2 實現思路3.1.3 代…

07 編譯器

目錄 編譯過程編譯器查看詳解函數庫自動化構建工具進度條程序 1. 編譯過程 預處理: a. 去注釋 b.宏替換 c.頭文件展開 d.條件編譯 編譯: 匯編 匯編: 可重定向二進制目標文件 鏈接: 鏈接多個.o, .obj合并形成一個可執行exe gcc編譯c程序, g編譯c程序 2. 編譯器查看 輸入gcc …

mac蘋果電腦c盤滿了如何清理內存?2024最新操作教程分享

蘋果電腦用戶經常會遇到麻煩:內置存儲器(即C盤)空間不斷縮小&#xff0c;電腦運行緩慢。在這種情況下&#xff0c;蘋果電腦c盤滿了怎么清理&#xff1f;如何有效清理和優化存儲空間&#xff0c;提高計算機性能&#xff1f;成了一個重要的問題。今天&#xff0c;我想給大家詳細介…

備戰藍橋杯---線段樹基礎2

今天我們把線段樹的另一個模板看一下&#xff1a; 在這里&#xff0c;我們注意到乘的操作&#xff0c;因此我們用兩個懶標記來分別表示加和乘&#xff0c;這時我們面臨了一個問題&#xff0c;就是當我們把標記往下傳時&#xff0c;它的兒子怎么知道是先乘還是先加&#xff1f; …

2025張宇考研數學,百度網盤視頻課+36講PDF講義+真題

張宇老師的課屬于幽默生動&#xff0c;會讓一個文科生愛上數學&#xff0c;但是有的同學不知道在哪看&#xff0c;可以看一下&#xff1a;2025張宇考研數學全程網盤 docs.qq.com/doc/DTmtOa0Fzc0V3WElI 可以粘貼在瀏覽器 張宇30講作為一本基礎講義&#xff1a;和教材…

java的線程池介紹

什么是線程池&#xff1f; 線程池是一種用于管理和復用線程的機制&#xff0c;旨在減少線程的創建和銷毀次數&#xff0c;提高線程的可重用性和執行效率。通過線程池&#xff0c;可以控制線程的數量、數量大小以及線程的執行方式&#xff0c;從而更加有效地處理并發任務。 線…

代碼隨想錄刷題第48天

今天來看看股票市場。第一題是買賣股票的最佳時機https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/description/&#xff0c;首先想到了暴力解法&#xff0c;兩層for循環&#xff0c;時間復雜度為n * n&#xff0c;代碼超時了。 class Solution { public:int m…