C++?多態!!!

一、引言

? ? ? ? 眾所周知,C++有三大特性,它們分別是封裝、繼承和多態,在之前的文章中已經詳細介紹過封裝和繼承了,今天我們將一起學習多態相關的知識,如果還想了解封裝、繼承相關的知識,可以跳轉到以下鏈接:
? ? ? ?

????????1、封裝:C++?類和對象(上)!!!-CSDN博客

? ? ? ? 2、繼承:C++?繼承!!!-CSDN博客

二、多態的概念

? ? ? ? 1、概念

? ? ? ? 通俗來講,多態表示多種狀態,即就是說當面對不同類型、不同特點的對象時,處理一個問題時采用不同的方式從而產生不同的效果,這就是多態

? ? ? ? 2、分類

? ? ? ? 事實上,多態細分之下有兩種,它們分別是靜態多態和動態多態,我們常說的多態事實上代指動態多態,也就是我們今天將要主要討論的內容,在詳細了解了多態的相關知識之后我們將再來理解這兩個概念

? ? ? ? 3、從實際的角度認識多態

? ? ? ? 上面我們介紹了多態的概念,這樣我們可以按圖索驥,大概舉幾個日常生活中常見的多態的實際應用:


? ? ? ? ????????(1).打滴滴

? ? ? ? ? ? ? ? 在打滴滴時,新人用戶常常會享受較大的優惠力度,小編記得在我第一次打滴滴時,價格優惠到了4元,那天的路程還挺遠的,如果放在今天可能會在十元往上,這里就用到了多態的相關知識(猜測),當一個新人用戶和一個老用戶同樣的調用"打車"接口時,卻對應了不同的優惠力度,這正好對應了多態的概念

? ? ? ? ? ? ? ? (2).買票系統

? ? ? ? ? ? ? ? 我們日程生活中會進行各種各樣的買票操作,比如各個景點或者是買回家的車票,不難發現,常見的對象會被平臺分為:普通身份、學生、軍人等

? ? ? ? ? ? ? ? 當這些對象同樣調用買票接口時,普通身份會全家買票,學生是半價買票,軍人常見的則是優先買票,很明顯,不同的對象調用同一接口,產生了不同的效果,對應了多態的概念

? ? ? ? 通過以上兩個常見的概念,我們可以感受到多態的相關知識是存在在我們生活中的方方面面的

三、多態的定義及實現

? ? ? ? 1、虛函數

? ? ? ? 虛函數:即就是被virtual關鍵字修飾的函數:
????????

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

? ? ? ? 2、虛函數的重寫

? ? ? ? 虛函數的重寫:派生類中有一個函數跟基類的虛函數三同(即函數名、函數參數、函數返回值都相同)的函數,那么就稱該派生類重寫(覆蓋)了基類的虛函數,例如:
????????

class Person
{
public:virtual void buy_t(){cout << "全價購票" << endl;}
};
class Student : public Person
{
public:void buy_t(){cout << "半價購票" << endl;}
};

? ? ? ? 以上的情況我們就說Student類重寫了Person類中的buy_t函數

? ? ? ? 但是需要注意的是,虛函數重寫存在以下兩個例外:
? ? ? ? ? ? ? ?

????????????????(1).協變(基類與派生類函數返回值不相同)

? ? ? ? ? ? ? ? 派生類重寫基類虛函數是,與基類函數返回值類型不同,當且僅當一個繼承體系的返回值對應的返回了一個繼承體系(并不限制一定是本地的繼承體系)的指針或引用,這時候仍然構成虛函數重寫,稱為協變(了解即可,不推薦使用)例如:
????????????????

class A{};class B : public A {};class Person {public:virtual A* f() {return new A;}};class Student : public Person {public:virtual B* f() {return new B;}};
? ? ? ? ? ? ? ? (2).析構函數的重寫(基類與派生類析構函數名不相同)

????????????????如果基類的析構函數為虛函數,此時派生類析構函數只要定義,無論是否加virtual關鍵字, 都與基類的析構函數構成重寫,雖然基類與派生類析構函數名字不同。雖然函數名不相同, 看起來違背了重寫的規則,其實不然,這里可以理解為編譯器對析構函數的名稱做了特殊處 理,編譯后析構函數的名稱統一處理成destructor

? ? ? ? ? ? ? ? 所以為什么要這樣特殊處理析構函數,使它可以構成虛函數重寫呢?,我們從下面一個例子來看:
????????????????

class Person {public:virtual ~Person() {cout << "~Person()" << endl;}};class Student : public Person {public:virtual ~Student() { cout << "~Student()" << endl; }};// 只有派生類Student的析構函數重寫了Person的析構函數,下面的delete對象調用析構函
數,才能構成多態,才能保證p1和p2指向的對象正確的調用析構函數。
int main(){Person* p1 = new Person;Person* p2 = new Student;
delete p1;delete p2;}return 0;

? ? ? ? ? ? ? ? 上面的代碼中,p1、p2都是Person*的變量,隨后調用delete對這兩個動態申請的空間進行釋放,事實上delete對于自定義類型會調用對應類的析構函數,此時就產生了一個問題:兩個空間都會調用Person的析構函數,這是我們不想看到的,我們希望的是對于p1調用Person的析構函數,而對于p2則是調用Student的析構函數

? ? ? ? ? ? ? ? 這時候我們可以認真的觀察一下我們上面的需求,好像就是使用基類的指針來調用同一個函數,同時我們想讓該調用動作對于不同的對象產生不同的效果,是的,這就是我們前面多態討論過的需求,現在只有一個條件還沒有滿足,就是函數名并不相同,所以我們順理成章的想到要讓編譯器對析構函數名進行特殊處理,這樣在將基類的析構函數寫為虛函數時,自然的就解決了上面的問題

? ? ? ? 3、多態的構成條件

? ? ? ? 多態是在繼承關系中,不同的類對象調用同一函數,產生了不同的行為,比如Student繼承了Person,這時候Person對象全價買票,Student對象半價買票,所以首先的,多態是存在在繼承關系中的

? ? ? ? 在繼承關系中要構成多態還有兩個條件:
? ? ? ? ? ? ? ? (1).必須通過基類的指針或者飲用調用函數

? ? ? ? ? ? ? ? (2).被調用的函數必須是虛函數,同時派生類對基類的虛函數進行重寫

? ? ? ? 下面是構成多態的一個完整例子:

????????

#include <iostream>
using namespace std;
//多態
class Person
{
public:virtual void buy_t(){cout << "全價購票" << endl;}
};
class Student : public Person
{
public:void buy_t(){cout << "半價購票" << endl;}
};
void func(Person& rp)
{rp.buy_t();
}
int main()
{Person p;Student s;func(p);func(s);return 0;
}

? ? ? ? 這一段代碼的運行結果如下:
????????

四、C++11中提供的兩個相關的關鍵字:override和final

? ? ? ? 經過上面的講解,我們發現,C++中構成重寫從而構成多態的過程時非常嚴格的,而在平常的代碼工作中我們很容易會犯一些錯誤,比如:大小寫的問題、字母順序的問題,這些問題產生時是很難發現的,對于這些問題,只是沒有構成重寫,但并沒有編譯、鏈接的錯誤,不會報錯,非常頭疼,所以在C++11中我們提供了override和final兩個關鍵字,它們兩個可以幫助我們檢查這一類問題

? ? ? ? 1、final:該關鍵字有兩個作用

? ? ? ? ? ? ? ? (1).修飾虛函數,被修飾的函數不能被重寫:
????????????????
class Person
{
public:virtual void buy_t  ()final//final修飾了該函數{cout << "全價購票" << endl;}
};
class Student : public Person
{
public:void buy_t()//這個位置會報錯:無法重寫“final”函數 "Person::buy_t"{cout << "半價購票" << endl;}
};
????????????????(2).修飾一個類,被修飾的類不能被繼承??

????????????????

#include <iostream>
using namespace std;
//多態
class Person final//使用final修飾這個類
{
public:virtual void buy_t(){cout << "全價購票" << endl;}
};
class Student : public Person//這個位置會報錯:不能將"final"類類型用作基類
{
public:void buy_t(){cout << "半價購票" << endl;}
};

? ? ? ? 2、override:檢查派生類函數是否重寫了基類某個虛函數,如果沒有就報錯

????????

class Person
{
public:virtual void buy_t(){cout << "全價購票" << endl;}
};
class Student : public Person
{
public:void buy_tx() override//override修飾該函數//該位置報錯:使用override修飾的函數不能重寫基類成員{cout << "半價購票" << endl;}
};

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

六、抽象類

? ? ? ? 1、概念

? ? ? ? 在虛函數的函數頭之后加上=0,此時該函數被稱為純虛函數,包含純虛函數的類叫做抽象類(也叫做接口類),抽象類不能實例化出對象。派生類繼承之后也不能實例化出對象,只有重寫了純虛函數,派生類才能實例化出對象,純虛函數規范了派生類必須重寫,它更能體現出接口繼承

? ? ? ? 下面的代碼體現出了這種接口繼承的思想:
????????

#include <iostream>
using namespace std;
//多態
class Person
{
public:virtual void buy_t() = 0;};
class Student : public Person
{
public:void buy_t(){cout << "半價購票" << endl;}
};
class Teacher :public Person
{
public:void buy_t(){cout << "十倍價錢購票" << endl;}};
void func(Person& rp)
{rp.buy_t();
}
int main()
{Teacher t;Student s;func(t);func(s);return 0;
}

? ? ? ? 下面是以上代碼的執行結果:
????????? ? ? ?

? ? ? ? 2、接口繼承和實現繼承

?????????普通函數的繼承是一種實現繼承,派生類繼承了基類函數,可以使用函數,繼承的是函數的實 現。虛函數的繼承是一種接口繼承,派生類繼承的是基類虛函數的接口,目的是為了重寫,達成 多態,繼承的是接口。所以如果不實現多態,不要把函數定義成虛函數

七、多態的原理

? ? ? ? 1、虛函數表

? ? ? ? ? ? ? ? (1).引入
// 這里常考一道筆試題:sizeof(Base)是多少?
class Base{public:virtual void Func1(){cout << "Func1()" << endl;}private:int _b = 1;};

? ? ? ? ? ? ? ? 我們先通過打印的方式看一下這個問題的結果是多少?

????????????????

? ? ? ? ? ? ? ? (2).解決問題

? ? ? ? ? ? ? ? 可以看到,結果輸出了8(這里要強調一下,小編實在x86的環境下輸出的,環境或者平臺改變可能會影響結果),這是為什么呢?或許含有虛函數的類對象進行了一些特殊處理?接下來我們通過調試的方法來看一下該類對象模型是怎樣的:

????????????????

? ? ? ? ? ? ? ? 經過上面的調試窗口我們知道,原來在Base類中除了_b成員,還多一個__vfptr放在對象的前面(注意有些 平臺可能會放到對象的最后面,這個跟平臺有關),對象中的這個指針我們叫做虛函數表指針(v代 表virtual,f代表function)。一個含有虛函數的類中都至少都有一個虛函數表指針,因為虛函數 的地址要被放到虛函數表中,虛函數表也簡稱虛表,那么派生類中這個表放了些什么呢?我們接著往下分析

? ? ? ? ? ? ? ? 為了符合多態的情景,我們先對上面的代碼做出以下改造:
????????????????

// 針對上面的代碼我們做出以下改造
// 1.我們增加一個派生類Derive去繼承Base// 2.Derive中重寫Func1// 3.Base再增加一個虛函數Func2和一個普通函數Func3class Base{public:virtual void Func1(){cout << "Base::Func1()" << endl;}virtual void Func2(){cout << "Base::Func2()" << endl;}void Func3(){cout << "Base::Func3()" << endl;}private:int _b = 1;};class Derive : public Base{public:virtual void Func1(){cout << "Derive::Func1()" << endl;
}private:int _d = 2;};int main(){Base b;Derive d;return 0;}

? ? ? ? ? ? ? ? 接下來我們一起觀察這個加強版繼承體系的類對象模型,從而說明派生類中的虛表有什么不同?

????????????????

? ? ? ? ? ? ? ? 可以觀察到:繼承之后的d對象模型中分為兩個部分,分別是Base部分和自己的成員,而在Base部分中也有一個_vfptr指針,這意味著d不會生成自己的虛表指針,而是以繼承的形式沿用了Base類的指針,而兩個指針指向的位置是不同的,這就是說兩個類的虛表是不同的,事實上的確是這樣的,派生類會首先繼承基類的虛表,然后對于重寫過的函數將新的函數指針覆蓋原本的函數指針,形成了屬于自己的虛表

? ? ? ? 2、多態的實現

? ? ? ? 經過上面對于虛表指針和虛表的認識,我們大概也可以想到多態究竟是如何實現的

? ? ? ? 事實上,多態的實現原理就是虛表指針存在在父子類中基類的部分,所以必須使用基類的指針或者引用調用(不能直接使用對象調用是因為對象的切片賦值會丟失信息,而指針和引用的切片賦值不會),同時通過虛表指針我們就可以找到虛表,父子類的虛表不同,找到的函數也就不同,這時候就實現了多態調用函數

? ? ? ? 3、動態綁定與靜態綁定

? ? ? ? ????????(1). 靜態綁定又稱為前期綁定(早綁定),在程序編譯期間確定了程序的行為,也稱為靜態多態, 比如:函數重載

? ? ? ? ? ? ? ? (2).?動態綁定又稱后期綁定(晚綁定),是在程序運行期間,根據具體拿到的類型確定程序的具體 行為,調用具體的函數,也稱為動態多態

八、結語

? ? ? ? 這就是本期有關多態的全部內容了,感謝大家的閱讀,歡迎各位于晏、亦菲和我一起交流、學習、進步!!!? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 、? ? ? ? ? ? ? ??
????????????????

? ? ? ? ? ? ??

????????? ? ? ? ? ? ? ? ? ? ??

????????????????? ? ? ??

????????

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

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

相關文章

electron安裝報錯處理

electron安裝報錯 解決方法&#xff1a; 修改 C:\Users\用戶名.npmrc下配置文件 添加代碼 electron_mirrorhttps://cdn.npmmirror.com/binaries/electron/ electron_builder_binaries_mirrorhttps://npmmirror.com/mirrors/electron-builder-binaries/最后代碼 registryhtt…

Windows10下使用QEMU安裝Ubuntu20.04虛擬機,并啟用硬件加速

Windows10下使用QEMU安裝Ubuntu20.04虛擬機&#xff0c;并啟用硬件加速 作者將狼才鯨創建日期2025-05-30 CSDN閱讀地址&#xff1a;Windows10下使用QEMU安裝Ubuntu20.04虛擬機&#xff0c;并啟用硬件加速 本文檔源碼地址&#xff1a;Windows10下使用QEMU安裝Ubuntu20.04虛擬機…

頂刊SCS | 基于視覺語言大模型推理分割的建筑足跡尺度功能分類, 樣本數據和代碼已開源!

論文介紹 題目&#xff1a;Visual-language reasoning segmentation (LARSE) of function-level building footprint across Yangtze River Economic Belt of China 期刊&#xff1a;Sustainable cities and society&#xff08;中科院一區TOP&#xff0c;IF10.5&#xff09;…

【軟件】navicat 官方免費版

Navicat Premium Lite https://www.navicat.com.cn/download/navicat-premium-lite

每個路由器接口,都必須分配所屬網絡內的 IP 地址,用于轉發數據包

在IP網絡中&#xff0c;主機&#xff08;Host&#xff09;和路由器接口&#xff08;Router Interface&#xff09;都需要分配網絡地址&#xff08;IP地址&#xff09;。 1. 主機&#xff08;Host&#xff09;的IP地址分配 (1) 作用 主機的IP地址用于唯一標識該設備&#xff0…

鴻蒙OSUniApp頁面切換動效實戰:打造流暢精致的轉場體驗#三方框架 #Uniapp

UniApp頁面切換動效實戰&#xff1a;打造流暢精致的轉場體驗 引言 在移動應用開發中&#xff0c;頁面切換動效不僅能提升用戶體驗&#xff0c;還能傳達應用的品質感。隨著HarmonyOS的普及&#xff0c;用戶對應用的動效體驗要求越來越高。本文將深入探討如何在UniApp中實現流暢…

Tesseract OCR 安裝與中文+英文識別實現

一、下載 https://digi.bib.uni-mannheim.de/tesseract/ 下載&#xff0c;盡量選擇時間靠前的&#xff08;識別更好些&#xff09;。符合你的運行機&#xff08;我的是windows64&#xff09; 持續點擊下一步安裝&#xff0c;安裝你認可的路徑即可&#xff0c;沒必要配置環境變…

Visual Studio 2022 發布獨立的 exe 文件

我們在用 Visual Studio 2022 寫好一個 exe 程序之后&#xff0c;如果想把這個拿到其他地方運行&#xff0c;需要把 exe 所在的文件夾一起拿過去。 編譯出來的 exe 文件需要其他幾個文件一同放在同一目錄才能運行&#xff0c;原因在于默認情況下&#xff0c;Visual Studio 是把…

Kotlin-特殊類型

文章目錄 數據類型枚舉類型匿名類和伴生對象單例類伴生對象 數據類型 聲明一個數據類非常簡單: //在class前面添加data關鍵字表示為一個數據類 data class Student(var name: String, var age: Int)數據類聲明后,編譯器會根據主構造函數中聲明的所有屬性自動為其生成以下函數…

在線博客系統【測試報告】

&#x1f552; 一. 項目背景 由于紙質筆記容易丟失&#xff0c;攜帶不變&#xff0c;為了方便自己學習的過程中記錄筆記&#xff0c;特開發了這個博客系統。這個系統后端采用 SpringBoot MyBatis SpringMVC &#xff1b;前端使用Html CSS JS&#xff1b;數據庫使用的是Mysq…

每日刷題c++

快速冪 #include <iostream> using namespace std; #define int long long int power(int a, int b, int p) {int ans 1;while (b){if (b % 2){ans * a;ans % p; // 隨時取模}a * a;a % p; // 隨時取模b / 2;}return ans; } signed main() {int a, b, p;cin >> a …

Python中的變量、賦值及函數的參數傳遞概要

Python中的變量、賦值及函數的參數傳遞概要 python中的變量、賦值 python中的變量不是盒子。 python中的變量無法用“變量是盒子”做解釋。圖說明了在 Python 中為什么不能使用盒子比喻&#xff0c;而便利貼則指出了變量的正確工作方式。 如果把變量想象為盒子&#xff0c;那…

KVM 安裝 Ubuntu 22

在 KVM 中安裝 Ubuntu 22 虛擬機。 首先創建硬盤文件 sudo qemu-img create -f qcow2 /app/vms/ubuntu22.qcow2 100G安裝Ubuntu 22 sudo virt-install \--name ubuntu22 \--ram 4096 \--vcpus 2 \--disk path/app/vms/ubuntu22.qcow2,formatqcow2 \--os-type linux \--os-va…

基于生產-消費模式,使用Channel進行文件傳輸(Tcp方式)

Client端&#xff1a; #region 多文件傳輸 public class FileMetadata {public string FileName { get; set; }public long FileSize { get; set; } }class Program {const int PORT 8888;const int BUFFER_SIZE 60 * 1024 * 1024;//15s-50 25s-64 33s-32 27s-50 31s-40 25…

【后端高階面經:Elasticsearch篇】39、Elasticsearch 查詢性能優化:分頁、冷熱分離與 JVM 調優

一、索引設計優化:構建高效查詢的基石 (一)分片與副本的黃金配置 1. 分片數量計算模型 # 分片數計算公式(單分片建議30-50GB) def calculate_shards(total_data_gb, single_shard_gb=30):return max

學習路之PHP--easyswoole3.3安裝入門

學習路之PHP--easyswoole安裝入門 一、安裝swoole擴展二、安裝easyswoole三、指定PHP版本安裝四、啟動swoole五、EasySwoole的入門學習如果報&#xff1a;not controller class match 六、學習推薦&#xff1a; 0、centos 7、php7.2.33、easyswoole 3.3 一、安裝swoole擴展 二、…

Ad Hoc

什么是 Ad Hoc&#xff1f; Ad hoc 一詞源于拉丁語&#xff0c;意為“為此目的”或“為此特定原因”。一般來講&#xff0c;它指的是為解決某一特定問題或任務&#xff08;而非為了廣泛重復應用&#xff09;而設計的行動、解決方案或組合。在加密貨幣和區塊鏈領域&#xff0c;…

Lines of Thought in Large Language Models

Lines of Thought in Large Language Models 《Lines of Thought in Large Language Models》(大語言模型中的思維鏈)聚焦于分析大語言模型(LLMs)在生成文本時,其內部向量軌跡的統計特性。 核心目標是揭示LLMs復雜的“思維過程”(即文本生成時的隱藏狀態變化)能否被簡…

npm/yarn/pnpm安裝時Sharp模塊報錯解決方法

在安裝依賴模塊時&#xff0c;npm/yarn/pnpm安裝時Sharp模塊報錯解決方法。 打開源代碼發現&#xff1a;使用的下載地址是github地址&#xff0c;就是因為國內經常無法訪問github造成的。 解決辦法&#xff1a; 把涉及到的下載包設置不要從github上下載&#xff0c;設置成淘寶…

基于CEEMDAN-Transformer-BiLSTM的多特征風速氣候預測的完整實現方案及PyTorch源碼解析

基于CEEMDAN-Transformer-BiLSTM的多特征風速氣候預測的完整實現方案及PyTorch源碼解析 一、模型架構設計 1.1 整體框架 該模型采用三級架構設計&#xff08;圖1&#xff09;&#xff1a; CEEMDAN分解層&#xff1a;對非平穩風速序列進行自適應分解多模態特征融合模塊&#…