C++之多態(從0到1的突破)

世間百態,每個人都扮演著不同的角色,都進行著不同的行為。C++更是如此,C++中也會出現有著不同行為的多種形態的出現,那就讓我們一起進入C++的多態世界吧!!!

一. 多態的概念

多態,顧名思義,就是多種形態。多態分為編譯時多態(靜態多態)和運?時多態(動態多態)。編譯時
多態(靜態多態)就是函數重載和函數模板,它們傳不同類型的參數就可以調?不同的函數,通過參數不同達到多種形態,之所以叫編譯時多態,是因為它們實參傳給形參的參數匹配是在編譯時完成的,我們把編譯時?般歸為靜態,運?時歸為動態。運?時多態,就是傳不同的對象來完成不同的?為,從而達到多種形態。

二. 多態的定義和實現

1.多態的構成條件

多態是?個繼承關系下的類對象,去調?同?函數,產?了不同的?為。

注意: 實現多態還有兩個必須重要條件

① 必須是基類的指針或者引?調?虛函數。

② 被調?的函數必須是虛函數,并且完成了虛函數重寫/覆蓋。

原因:要實現多態,第?必須是基類的指針或引?,因為只有基類的指針或引?才能既指向基類
對象?指向派?類對象;第?派?類必須對基類的虛函數完成重寫/覆蓋,重寫或者覆蓋了,基類和派
?類之間才能有不同的函數,多態的不同形態效果才能達到。

2. 虛函數

類成員函數前?加virtual修飾,那么這個成員函數被稱為虛函數。注意?成員函數不能加virtual修
飾。

示例:

class Person
{
public:virtual void show() { cout << "show()" << endl;}
};virtual void f1()  //報錯,非成員函數不能用virtual修飾
{}

3. 虛函數的重寫/覆蓋

派?類中有?個跟基類完全相同的虛函數(即派?類虛函數與基類虛函數的返回值類型、函數名、參數類型列表完全相同),則稱派?類的虛函數重寫/覆蓋了基類的虛函數。

注意:在重寫基類虛函數時,派?類的虛函數在不加virtual關鍵字時,雖然也可以構成重寫但是該種寫法不是很規范,不建議這樣使?。

示例:

#define _CRT_SECURE_NO_WARNINGS  1
#include <iostream>
using namespace std;//多態的概念、定義和實現class Person {      //Person是父類
public:virtual void BuyTicket() { cout << "買票-全價" << endl; }
};class Student : public Person {   //Student是子類
public://虛函數重寫virtual void BuyTicket() { cout << "買票-打折" << endl; }
};void Func(Person* ptr)
{//這里的函數BuyTicket()的行為和ptr指向的對象有關ptr->BuyTicket();
}//virtual void f1()  //報錯,非成員函數不能用virtual修飾
//{}int main()
{Person ps;Student st;Func(&ps);Func(&st);ps.BuyTicket();st.BuyTicket();return 0;
}

多態示例:

class Animal  //父類
{
public:virtual void talk() const{}
};class Dog : public Animal  //子類
{
public:virtual void talk() const{cout << "汪汪" << endl;}
};class Cat : public Animal  //子類
{
public:virtual void talk() const{cout << "喵喵" << endl;}
};void letsHear(const Animal& animal)
{animal.talk();
}int main()
{Cat cat;Dog dog;letsHear(cat);letsHear(dog);return 0;
}

多態應用:

class A
{
public:virtual void func(int val = 1) { cout << "A->" << val <<endl; }virtual void test() { func(); }  //test()的A* this,把B*對象傳給this
};class B : public A
{
public:void func(int val = 0) { cout << "B->" << val << endl; }
};int main(int argc, char* argv[])
{B* p = new B;//A* p1 = new A;p->test();  //輸出B->1p->func();  //輸出B->0return 0;
}

4. 虛函數重寫中的協變

派?類在重寫基類虛函數時,與基類虛函數的返回值類型不同。即基類虛函數返回基類對象的指針或者引?,派?類虛函數返回派?類對象的指針或者引?時,稱為協變。協變的實際運用很少。

示例:

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;
}

5. 析構函數的重寫

當基類的析構函數為虛函數時,派?類的析構函數只要定義了,?論是否加virtual關鍵字,都與基類的析構函數構成重寫,雖然基類與派?類析構函數名字不同看起來不符合重寫的規則,但實際上編譯器對這里的析構函數的名稱做了特殊處理,編譯后析構函數的名稱統?處理成destructor,所以基類的析構函數加了virtual修飾,派?類的析構函數就構成重寫。

下?的代碼我們可以看到,如果~A(),不加virtual,那么delete p2時只調?的A的析構函數,沒有調?
B的析構函數,就會導致內存泄漏問題,因為~B()中有資源需要釋放。

示例:

class A
{
public://~A()  //有問題,會導致內存泄漏virtual ~A(){cout << "~A()" << endl;}
};class B : public A {
public:~B(){cout << "~B()->delete:" << _p << endl;delete _p;}
protected:int* _p = new int[10];
};// 只有子類Student的析構函數重寫了父類Person的析構函數,下?的delete對象調?析構函數時才能
//構成多態,才能保證p1和p2指向的對象正確的調?析構函數,防止內存泄漏。
int main()
{A* p1 = new A;A* p2 = new B;delete p1;delete p2;return 0;
}

6. override和final關鍵字

C++11提供了override關鍵字,可以幫助用戶檢查虛函數是否重寫。如果我們不想讓子類重寫這個虛函數,那么可以?final去修飾基類的虛函數。

示例1:

class Car {
public:virtual void Dirve(){}
};class Benz :public Car {
public://報錯,override修飾的成員函數不能重寫基類成員//virtual void Drive() override { cout << "Benz-comfort" << endl; }
};int main()
{return 0;
}

示例2:

class Car
{
public:virtual void Drive() final {}
};class Benz :public Car
{
public://報錯,基類中被final修飾的成員函數不能被子類重寫//virtual void Drive() { cout << "Benz-comfort" << endl; }
};int main()
{return 0;
}

7. 重載/重寫/隱藏的對比

在這里插入圖片描述

注意: 針對多態,重定義也叫隱藏。

三. 純虛函數和抽象類

在虛函數的后?寫上 =0 ,則這個函數就為純虛函數,純虛函數可以定義實現但是沒有必要,只要聲明即可。包含純虛函數的類叫做抽象類,抽象類不能實例化出對象,如果派?類繼承后不重寫純虛函數,那么派?類也是抽象類。純虛函數某種程度上強制了派?類重寫虛函數,因為不重寫派生類不能實例化對象。

示例:

class Car   //抽象類
{
public:virtual void Drive() = 0; //純虛函數
};class Benz :public Car
{
public://若父類是抽象類,則未重寫父類純虛函數的子類也是抽象類virtual void Drive(){cout << "Benz-comfort" << endl;}
};class BMW :public Car
{
public:virtual void Drive(){cout << "BMW-control" << endl;}
};int main()
{//Car car;  //報錯,抽象類不能實例化對象Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();return 0;
}

四.多態的原理

1. 虛函數表指針

示例:

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
protected:int _b = 1;char _ch = 'x';
};int main()
{Base b;//12byte,每個有虛函數的類對象上都會在前面存一個_vfptr的虛函數表指針,指向一個虛函數表//虛函數表存儲該類的所有虛函數地址cout << sizeof(b) << endl;return 0;
}

如下圖:

在這里插入圖片描述

2. 多態的原理

示例:

class Person {
public:virtual void BuyTicket() { cout << "買票-全價" << endl; }virtual void func1() {}void func2() { }
private:string _name;
};class Student : public Person {
public:virtual void BuyTicket() { cout << "買票-打折" << endl; }virtual void func1() {}void func3() {}
private:string _id;
};class Soldier : public Person {
public:virtual void BuyTicket() { cout << "買票-優先" << endl; }
private:string _codename;
};void Func(Person* ptr)
{//這里的函數BuyTicket()的行為和ptr指向的對象有關ptr->BuyTicket();
}int main()
{//多態也會發?在多個派生類之間Person ps;Student st;Soldier sr;Func(&ps);Func(&st);Func(&sr);return 0;
}

通過以上代碼和下圖我們可以看到,滿?多態條件后,底層不再是編譯時通過調?對象來確定函數的地址,?是運?時到對象指向的虛表中確定對應的虛函數的地址,這樣就實現了指針或引?指向基類就調?基類的虛函數,指向派?類就調?派?類對應的虛函數。第?張圖,ptr指向的Person對象,調?的是Person的虛函數;第?張圖,ptr指向的Student對象,調?的是Student的虛函數。

在這里插入圖片描述
3. 動態綁定和靜態綁定

① 對不滿?多態條件的函數調?是在編譯時綁定,也就是編譯時確定所調?函數的地址,叫做靜態綁定。

② 滿?多態條件的函數調?是在運?時綁定,也就是在運?時到指針指向對象的虛函數表中找到所調?函數的地址,也就做動態綁定。

4. 虛函數表

①基類對象的虛函數表中存放基類所有虛函數的地址。同類型的對象共?同?張虛表,不同類型的對
象各自有獨?的虛表,所以基類和派?類有各自獨立的虛表。

② 派?類成員由兩部分構成,繼承下來的基類成員和自己的成員,?般情況下,繼承下來的基類中有虛函數表指針,派生類自己就不會再?成虛函數表指針。但是這?繼承下來的基類部分虛函數表指針和基類對象的虛函數表指針不是同?個,就像基類對象的成員和派?類對象中的基類對象成員一樣是獨立的。

③ 派?類中重寫的基類的虛函數,派?類的虛函數表中對應的虛函數就會被覆蓋成派?類重寫的虛函
數地址。

④ 派?類的虛函數表中包含三個部分:(1)基類的虛函數地址。(2)派?類重寫的虛函數地址完成覆蓋。(3)派?類自己的虛函數地址。

⑤ 虛函數表本質就是?個存儲虛函數表指針的指針數組。

⑥ 虛函數和普通函數?樣,編譯好后是?段指令,都是存在代碼段的,只是虛函數的地址?存到了虛表中。

⑦ 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:// 重寫基類的func1virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func1" << endl; }void func4() { cout << "Derive::func4" << endl; }
protected:int b = 2;
};int main()
{Base b;Derive d;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 b1;Derive d1;Base* p3 = &b1;Derive* p4 = &d1;printf("Person虛表地址:%p\n", *(int*)p3);printf("Student虛表地址:%p\n", *(int*)p4);printf("Base虛函數地址:%p\n", &Base::func1);printf("Base普通函數地址:%p\n", &Base::func5);return 0;
}

如圖:

在這里插入圖片描述

How time flies!!! 相信看到了這里的老鐵一定對C++的多態有了初步甚至說是更深的理解,那各位我們就后會有期了!!!

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

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

相關文章

路由器NAT的類型測定

目前所使用的NAT基本都是NAPT&#xff0c;即多端口的NAT技術&#xff0c;因此本文主要是設計了兩種測定路由器NAPT類型的實驗。 實驗環境 設備 主機A&#xff1a;Windows主機B&#xff1a;Windows路由器 軟件 ncWiresharkSocketTools 在局域網內部完成所有測試&#xff0c;完全…

ROS 2系統Callback Group概念筆記

核心概念 Callback Group&#xff08;回調組&#xff09;是一個管理一個或多個回調函數執行規則的容器。它決定了這些回調函數是如何被節點&#xff08;Node&#xff09;的 executor 調度的&#xff0c;特別是當多個回調函數同時就緒時&#xff0c;它們之間是并行執行還是必須串…

Qt——主窗口 mainWindow

主窗口 mainWindow 前面學習的所有代碼&#xff0c;都是基于QWidget控件&#xff0c;其更多的是作為別的窗口的部分 現在來學習QMainWindow&#xff0c;即主窗口&#xff0c;其包含以下屬性 Window Title&#xff1a;標題欄Menu Bar&#xff1a;菜單欄Tool Bar Area&#xff1a…

無訓練神經網絡影響下的智能制造

摘要 未訓練神經網絡&#xff08;Untrained Neural Networks, UNNs&#xff09;作為近年來人工智能領域的新興范式&#xff0c;正在逐步改變智能制造的發展路徑。不同于傳統深度學習依賴大規模標注數據與高性能計算資源的模式&#xff0c;UNNs 借助網絡結構自身的歸納偏置與初…

微服務自動注冊到ShenYu網關配置詳解

一、配置逐行詳解 shenyu:register:registerType: http # 注冊中心類型:使用 HTTP 協議進行注冊serverLists: ${shenyu-register-serverLists} # ShenYu Admin 的地址列表props:username: ${shenyu-register-props-username} # 注冊認證用戶名password: ${shenyu-regi…

時序數據庫IoTDB的列式存儲引擎

在大數據時代&#xff0c;工業物聯網&#xff08;IIoT&#xff09;場景正以前所未有的速度生成著海量的時間序列數據。這些數據通常由成千上萬的傳感器&#xff08;如溫度、壓力、轉速傳感器&#xff09;持續不斷采集產生&#xff0c;它們具備鮮明的特點&#xff1a;數據時間屬…

JavaScript手錄18-ajax:異步請求與項目上線部署

前言&#xff1a;軟件開發流程 AJAX&#xff1a;前端與后端的數據交互 前后端協作基礎 Web應用的核心是“數據交互”&#xff0c;前端負責展示與交互&#xff0c;后端負責處理邏輯與數據存儲&#xff0c;二者通過網絡請求協作。 &#xff08;1&#xff09;項目開發流程與崗…

HTB 賽季7靶場 - Enviroment

最近所幸得點小閑&#xff0c;補個檔嘞&#xff01;~nmap掃描 nmap -F -A 10.10.11.67dirsearch掃描發現login接口 http://environment.htb/login構造如下payload&#xff0c;讓程序報錯&#xff0c;其原理在于缺失了rember后會導致報錯&#xff0c;從而告訴我們一個新的參數ke…

源碼編譯部署 LAMP 架構詳細步驟說明

源碼編譯部署 LAMP 架構詳細步驟說明 一、環境準備 1. 關閉防火墻和SELinux [roothrz ~]# systemctl stop firewalld [roothrz ~]# systemctl disable firewalld [roothrz ~]# setenforce 02. 配置YUM網絡源 [roothrz ~]# curl -o /etc/yum.repos.d/CentOS-Base.repo https://m…

機器學習----PCA降維

一、PCA是什么&#xff1f;主成分分析&#xff08;Principal Component Analysis&#xff0c;PCA&#xff09;是機器學習中最常用的降維技術之一&#xff0c;它通過線性變換將高維數據投影到低維空間&#xff0c;同時保留數據的最重要特征。PCA由卡爾皮爾遜于1901年發明&#x…

ReactNative開發實戰——React Native開發環境配置指南

一、開發前準備 1. macOS平臺基礎工具安裝 brew install node18 brew install watchman brew install cocoapods2. 代理配置 npm config set proxy http://127.0.0.1:7890 npm config set https-proxy http://127.0.0.1:7890# 新增擴展建議&#xff08;可選配置&#xff09; ec…

差速轉向機器人研發:創新驅動的未來移動技術探索

在科技日新月異的今天&#xff0c;機器人技術作為智能制造與自動化領域的核心驅動力&#xff0c;正以前所未有的速度發展。其中&#xff0c;差速轉向機器人以其獨特的運動機制和廣泛的應用前景&#xff0c;成為了科研與工業界關注的焦點。本文旨在探討差速轉向機器人研發進展&a…

Wireshark捕獲電腦與路由器通信數據,繪制波形觀察

一、準備工作 電腦發出數據的波形圖繪制在我的另一篇博客有詳細介紹&#xff1a; 根據Wireshark捕獲數據包時間和長度繪制電腦發射信號波形-CSDN博客 路由器發送給電腦數據的波形圖繪制也在我的另一篇博客有詳細介紹&#xff1a; 根據Wireshark捕獲數據包時間和長度繪制路由…

汽車ECU實現數據安全存儲(機密性保護)的一種方案

一、 綜述在車輛ECU中總是有一些密鑰或重要數據需進行機密性保護&#xff0c;但因產品選型、成本等考慮&#xff0c;導致一些ECU的芯片不支持硬件安全模塊&#xff08;例如HSM、TEE等&#xff09;。此時&#xff0c;為保障數據的機密性&#xff0c;可考慮通過軟件實現數據的安全…

AI 效應: GPT-6,“用戶真正想要的是記憶”

每周跟蹤AI熱點新聞動向和震撼發展 想要探索生成式人工智能的前沿進展嗎&#xff1f;訂閱我們的簡報&#xff0c;深入解析最新的技術突破、實際應用案例和未來的趨勢。與全球數同行一同&#xff0c;從行業內部的深度分析和實用指南中受益。不要錯過這個機會&#xff0c;成為AI領…

云計算學習100天-第25天

部署LNMP環境安裝軟件#在前一天已經安裝nginx的基礎上安裝MariaDB&#xff0c;php和php-fpm yum -y install mariadb mariadb-server mariadb-devel php php-mysqlnd php-fpm #mariadb&#xff08;數據庫客戶端軟件&#xff09;、mariadb-server&#xff08;數據庫服務器軟件&…

細化的 Spring Boot 和 Spring Framework 版本對應關系

注:本文由ai輔助,個人整理,有問題可留言 Spring Boot 3.x 系列 (基于 Spring Framework 6.x) Spring Boot 版本 對應的 Spring Framework 版本 Java 支持版本 3.1.5 (最新) 6.0.15 Java 17+ 3.1.4 6.0.14 Java 17+ 3.1.3 6.0.12 Java 17+ 3.1.2 6.0.11 Java 17+ 3.1.1 6.0.…

PyTorch API 1

文章目錄torch張量創建操作索引、切片、連接與變異操作加速器生成器隨機采樣原地隨機采樣準隨機采樣序列化并行計算局部禁用梯度計算數學運算常量逐點運算歸約操作比較運算頻譜操作其他操作BLAS 和 LAPACK 運算遍歷操作實用工具符號數字導出路徑控制流優化方法操作符標簽torch.…

基于FPGA的實時圖像處理系統(2)——VGA顯示彩條和圖片

VGA顯示彩條和圖片 文章目錄VGA顯示彩條和圖片一、VGA簡介二、功能設計1、彩條設計2、圖片設計三、結果展示四、代碼一、VGA簡介 VGA(Video Graphics Array)是IBM在1987年隨PS/2機?起推出的?種視頻&#xff0c;具有分辨率?、顯?速率快、顏?豐富等優點&#xff0c;在彩 ?…

【網絡運維】Linux 文本處理利器:sed 命令

Linux 文本處理利器&#xff1a;sed 命令 sed 簡介 sed&#xff08;Stream Editor&#xff09;是一款非交互式的流編輯器&#xff0c;誕生于 1973–1974 年間的貝爾實驗室&#xff0c;由 McMahon 開發。它專為文本處理而生&#xff0c;功能強大&#xff0c;是 Linux 文本處理常…