多態(難的起飛)

注意? ?virtual關鍵字:

? ? 1、可以修飾原函數,為了完成虛函數的重寫,滿足多態的條件之一

? ?2、可以菱形繼承中,去完成虛繼承,解決數據冗余和二義性

兩個地方使用了同一個關鍵字,但是它們互相一點關系都沒有

虛函數重寫:

?

多態的條件:

1、虛函數的重寫

2、父類對象的指針或者引用去調用虛函數

必須是父類指針或者引用

不可以是子類因為父類不可以傳給子類

class Person
{
public:virtual void BuyTicket() { cout << "Person全票" << endl; }
};
class Student : public Person
{
public:virtual void BuyTicket() { cout << "Student半票" << endl; }
};
void func(Person& p1)
{p1.BuyTicket();
}
int main()
{Person p1;Student s1;func(p1);func(s1);return 0;
}

協變(是多態的一種特殊情況):

多態:

1、虛函數的重寫(必須要函數名、返回值、參數要相同)

2、父類對象的指針或者引用去調用虛函數

但是協變可以返回值可以不同

但是返回值必須是基類的指針或引用和子類的指針或引用

//class A
//{
//};
//class B :public A
//{
//}
//其他類的基類和派生類也可以
//class Person
//{
//public:
//	virtual A* BuyTicket() { cout << "Person全票" << endl; return nullptr; }
//};
//class Student : public Person
//{
//public:
//	virtual B* BuyTicket() { cout << "Student半票" << endl;  return nullptr; }
//};
//void func(Person& p1)
//{
//	p1.BuyTicket();
//}
//class Person
{
public:virtual Person* BuyTicket() { cout << "Person全票" << endl; return nullptr; }
};
class Student : public Person
{
public:virtual Student* BuyTicket() { cout << "Student半票" << endl;  return nullptr;}
};
void func(Person& p1)
{p1.BuyTicket();
}
int main()
{Person p1;Student s1;func(p1);func(s1);return 0;
}

析構函數:

面試題:析構函數需不需要加vitrual?

class Person
{
public:~Person() { cout << "~Person()" << endl; }};
class Student : public Person
{
public:~Student() { cout << "~Student()" << endl;}
};int main()
{Person* p1= new Student;delete p1;return 0;
}

這種情況下父類的指針指向了new Student 但是使用完會造成內存泄漏,父類的指針只會調用父類的析構函數去清理該指向部分的空間,但是我們需要清理子類的空間就要調用子類的析構函數,所以需要加virtual 構成虛函數的重寫,讓父類的指針調用構成多態,就可以調用子類的析構函數。

?

看下一道面試題:

在做面試題之前先看下面代碼

在繼承關系中,

如何理解上述話呢?

看下面代碼

在滿足多態的條件下,虛函數的繼承是繼承了接口,所以缺省值繼承了,但是子類要自己重寫實現

所以當父類中的有虛函數,子類的就可以不用加virtual,但是不規范

答案:是B

為什么多態就要繼承父類的接口?突然感悟

比喻:子類中的函數 drive(Banz* const this),父類也有(Car* const this)
??//子類這個this是接收不了父類的指針,只有父類的指針或引用才可以指向子類
???//所以這個繼承接口才需要繼承父類的接口----突然感悟

============下面代碼===============?

//作者:螞蟻捉蟲蟲
//鏈接:https ://www.zhihu.com/question/517444641/answer/2390138862
//來源:知乎
//著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
#include <iostream>       // std::cout
class Base {public:Base() {};virtual void func_a(int a = 0) {}; //這個是虛函數,子類只繼承接口,具體的實現,由子類去實現void func_b(int b) { std::cout << b + 10 << "\n"; }; //這個是實函數,其接口和實現,都會被子類繼承
};class Base_A : public Base {
public:void func_a(int a=15) { std::cout << a << "\n"; };
};class Base_B : public Base {
public:void func_a(int a) { std::cout << a + 15 << "\n"; };
};int main()
{Base_A a;Base_B b;a.func_a(); //僅僅繼承了基類的接口,但沒有繼承實現a.func_b(10); //繼承了基類的接口及實現std::cout << std::endl;b.func_a(10); //僅僅繼承了基類的接口,但沒有繼承實現b.func_b(10); //繼承了基類的接口及實現return 0;
}

?

只有在滿足多態的情況下,虛函數的繼承才是父類的虛函數繼承對于子類來說繼承的是父類的接口(包括缺省值),子類函數的實現需要子類來寫

上述代碼只是完成了重寫,并沒有滿足多態,所以并沒有繼承接口

關鍵字final和override

1、final修飾虛函數,表示該虛函數不能再被繼承

也可以修飾class叫最終類不能被繼承

override關鍵字:檢查子類的虛函數是否完成重寫

構成虛函數重寫嗎?

沒有,認真看,但是不會報錯,所以,加上override就可以自動檢測檢查子類的虛函數是否完成重寫

重載、重寫、重定義

抽象類

可以看下列代碼:

//作者:螞蟻捉蟲蟲
//鏈接:https://www.zhihu.com/question/517444641/answer/2390138862
//來源:知乎
//著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。class Base {public:Base(){};virtual void func_a(int a) = 0; //這個是純虛函數,子類只繼承接口,具體的實現,由子類去實現void func_b(int b) {std::cout << b+10 << "\n";}; //這個是實函數,其接口和實現,都會被子類繼承
};class Base_A: public Base{
public:void func_a(int a){std::cout << a << "\n";};
};class Base_B: public Base{
public:void func_a(int a){std::cout << a + 15 << "\n";};
};int main ()
{Base_A a;Base_B b;a.func_a(10); //僅僅繼承了基類的接口,但沒有繼承實現a.func_b(10); //繼承了基類的接口及實現std::cout << std::endl;b.func_a(10); //僅僅繼承了基類的接口,但沒有繼承實現b.func_b(10); //繼承了基類的接口及實現return 0;
}

上述代碼里,定一個基類,里面有兩個成員函數,一個是虛函數,一個是實際函數;然后又定義了兩個子類,Base_A和Base_B,兩個子類對基類中的func_b函數有不一樣的實現

純虛函數的作用強制子類完成重寫

表示抽象的類型。抽象就是在現實中沒有對應的實體的

接口繼承和實現繼承

多態的原理:

測試我們發現b對象是8個字節,除了_b成員,還多了一個指針_vfptr放在對象對面,我們叫做虛函數指針我們叫做虛函數表指針。一個含有虛函數表的類中至少都有一個虛函數表指針,因為虛函數的地址要被放到虛函數表中,虛函數表稱虛表

注意:虛函數存放在哪里? 虛表存在哪里

虛表存的是虛函數指針,不是虛函數,虛函數也是函數所以也是存在代碼區,只是它的地址被存進虛函數指針中,這個指針被虛表記錄著

重寫:接口繼承,實現重寫,在原理上是覆蓋將父類繼承下來的vfptr的父類虛函數的地址覆蓋成子類的虛函數地址

從反匯編看原理:

普通類函數:

在編譯的過程中就已經確定了調用函數的地址

現在我們加上virtual虛函數

進入匯編,當形成多態時是如何調用的?

00B021E1 8B 45 08             mov         eax,dword ptr [A]  //將A指向空間地址給eax
00B021E4 8B 10                mov         edx,dword ptr [eax]  //將eax空間中的前四個字節地址給edx就是虛函數表指針
00B021E6 8B F4                mov         esi,esp//這個是維護函數棧幀的寄存器,不用管  
00B021E8 8B 4D 08             mov         ecx,dword ptr [A]  //將A指向空間地址給ecx
00B021EB 8B 42 04             mov         eax,dword ptr [edx+4]  //因為edx保存的是前四個字節空間的地址就是虛函數表指針+4就是run()的地址,將run()地址給eax,前4個是speak()的地址
00B021EE FF D0                call        eax //調用run()
00B021F0 3B F4                cmp         esi,esp 
00B021F2 E8 1A F1 FF FF       call        __RTC_CheckEsp (0B01311h) 

?多態就是有virtual函數是用虛函數表指針去存放虛函數的地址,在由虛函數表指針調用對應的函數

面試題:

虛函數存在哪里?代碼段,虛函數和普通函數一樣都是函數所以都是編譯成指令存進代碼段中

虛函數表存在哪里?

存在代碼段中,不是存在棧區,因為棧區是由一個個棧幀堆建的所以每調用創建一個對象就要建立一個虛表是很消耗內存的

證明一下:

虛表存放在代碼區中的代碼段最合適,堆區是動態開辟的數據區分為bss區(存放未初始化的static和未初始化的全局變量)數據區存放(存放初始化的static和初始化的全局變量),所以代碼段是最合適的

反向驗證:

發現很接近代碼區

總結:

多態的本質原理,符合多態的兩個條件。那么在父類的指針或引用調用時,會到指向對象的虛表找到對應的虛函數地址,進行調用

多態(程序運行時去指向對象的虛表中找到函數地址,進行調用,所以p指向誰就調用誰的虛函數)

普通函數的調用,編譯鏈接時確定函數的地址,運行時直接調用。類型時誰就是誰調用

動態綁定和靜態綁定:

編譯:就是代碼和語法檢查其實就是預處理、編譯、匯編、鏈接

運行:就是將可執行文件加載到內存中進行對數據區的數據替換

靜態綁定:更具調的類型就確定了調用的函數

動態綁定:運行時具體拿到類型確定程序的具體行為,就是在編譯時無法確定函數的行為

運行時根據寄存器去拿到函數的地址

單繼承和多繼承的虛表(不是虛基表)

單繼承:

void(*p)();? //函數指針

補充:

函數名就是函數的地址

那我們手動打印虛函數表

class base
{
public:virtual void func1() { cout << "base::func1()" << endl; }virtual void func2() { cout << "base::func2()" << endl; }};
class derive :public base
{
public:virtual void func1() { cout << "derive::func1()" << endl; }virtual void func3() { cout << "derive::func3()" << endl; }virtual void func4() { cout << "derive::func4()" << endl; }};
//void(*)()
typedef void(*VF_PTR)();//重命名函數指針void PrintVFTable(VF_PTR* pTable)//VF_PTR pTable[]  函數指針數組==虛函數表指針
{for (size_t i = 0; pTable[i] != 0; i++){printf("pTable[%d]=%p->", i, pTable[i]);VF_PTR f = pTable[i];//得到函數的地址==函數名f();}cout << endl;
}int main()
{base b1;derive d2;PrintVFTable((VF_PTR*)(*(int*)&b1));//取b1的地址因為要取到虛函數表指針,它在對象的前四個字節//所以轉換成int*在解引用就是取空間b1的前四個字節,因為此時是int*//所以要轉成VF_PTR*PrintVFTable((VF_PTR*)(*(int*)&d2));return 0;
}

多繼承的虛表:

計算一下test 對象等于多少?

class base
{
public:virtual void func1() { cout << "base::func1()" << endl; }virtual void func2() { cout << "base::func2()" << endl; }int i = 0;
};
class derive
{
public:virtual void func1() { cout << "derive::func1()" << endl; }virtual void func3() { cout << "derive::func3()" << endl; }virtual void func4() { cout << "derive::func4()" << endl; }int i = 0;
};
class test:public base,public derive
{
public:virtual void func3() { cout << "test::func1()" << endl; }virtual void func2() { cout << "test::func3()" << endl; }virtual void func7() { cout << "test::func4()" << endl; }
public:int i = 0;
};//void(*)()
typedef void(*VF_PTR)();//重命名函數指針void PrintVFTable(VF_PTR* pTable)//VF_PTR pTable[]  函數指針數組==虛函數表指針
{for (size_t i = 0; pTable[i] != 0; i++){printf("pTable[%d]=%p->", i, pTable[i]);VF_PTR f = pTable[i];//得到函數的地址==函數名f();}cout << endl;
}int main()
{test i;cout << sizeof(i) << endl;return 0;
}

等于20? ?

編譯器又沒顯示!!!那我們手動去看看

繼承的子類和其父類的表不是同一張表,只有同一類才是用一張表哦

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

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

相關文章

JAVASE總結一

1、 2、引用也可以是成員變量&#xff08;實例變量&#xff09;&#xff0c;也可以是局部變量&#xff1b;引用數據類型&#xff0c;引用&#xff0c; 我們是通過引用去訪問JVM堆內存當中的java對象&#xff0c;引用保存了java對象的內存地址&#xff0c;指向了JVM堆內存當中…

ESP32 - Micropython ESP-IDF 雙線教程 脈寬調制(PWM)(1)

ESP32 - Micropython ESP-IDF 雙線教程 脈寬調制&#xff08;PWM&#xff09; PWM 的基本原理PWM 的應用PWM 的優點PWM 的實現方式ESP32-micropython 中的 PWM 功能使用 micropython 控制 PWM 的代碼示例代碼介紹 ESP32-IDF 中的 PWM 功能1. 初始化配置函數2. 引腳綁定函數3. 占…

常見算法200個(5):快速排序(快排)

JS實現快速排序 1.快速排序思路&#xff1a; 選擇數組中的一個值作為基準&#xff0c;將數組中小于該值的數置于該數之前&#xff0c;大于該值的數置于該數之后&#xff0c;接著對該數前后的兩個數組進行重復操作直至排序完成。 2.代碼實現&#xff1a; function quick(arr)…

使用 Snort 進行入侵檢測

使用 Snort 進行入侵檢測 Snort 是一種流行的開源入侵檢測系統。您可以在http://www.snort.org/上獲取它。Snort 分析流量并嘗試檢測和記錄可疑活動。Snort 還能夠根據其所做的分析發送警報。 Snort 安裝 在本課中&#xff0c;我們將從源代碼安裝。此外&#xff0c;我們不會安…

2024 前端面試每日1小時

三日 1. 如何理解Vue的模板編譯原理 Vue的模板編譯實際就是將模板字符串通過解析、優化和代碼生成等步驟轉換為渲染函數的過程。這個過程中&#xff0c;AST扮演了非常重要的角色&#xff0c;它用樹形結構描述了模板的內容和結構&#xff0c;是編譯過程的核心數據結構&#xff…

MySQL——適合不適合創建索引的情況

那些情況適合創建索引 1、字段的數值具有唯一性的限制 索引本身可以起到約束的作用&#xff0c;比如唯一索引、主鍵索引都是可以起到唯一性約束的&#xff0c;因此在我們的數據表中&#xff0c;如果某個字段是唯一性的&#xff0c;就可以直接創建唯一性索引&#xff0c;或者主…

Nodejs 爬蟲 案例

1.安裝&#xff1a; npm install cheerio npm install axios2.介紹&#xff1a; 2.1 cheerio 特點和用途描述&#xff1a; HTML解析和操作&#xff1a;Cheerio 可以將 HTML 字符串加載到內存中&#xff0c;并將其轉換為一個可操作的 DOM 樹結構&#xff0c;從而可以方便地對…

AURIX TC3xx單片機介紹-啟動過程介紹1

從各個域控制器硬件解決方案來看,MPU可能來自多個供應商,有瑞薩,有NXP等,但對于MCU來說,基本都采用英飛凌TC3xx。 今天我們就來看一下TC3xx的啟動過程,主要包含如下內容: uC上電過程中,會經過一個上電時序,從復位狀態“脫離”出來;Boot Firmware是復位后第一個執行的…

使用 Effect 同步-09

有些組件需要與外部系統同步。例如&#xff0c;你可能希望根據 React state 控制非 React 組件、設置服務器連接或在組件出現在屏幕上時發送分析日志。Effects 會在渲染后運行一些代碼&#xff0c;以便可以將組件與 React 之外的某些系統同步。 簡單理解&#xff0c;就是需要操…

Python實現對Word文檔內容出現“重復標題”進行自動去重(4)

前言 本文是該專欄的第4篇,后面會持續分享Python辦公自動化干貨知識,記得關注。 在本專欄上一篇文章《Python實現對Word文檔內容出現“重復標題”進行自動去重(3)》中,筆者有詳細介紹使用python對word文檔內容的目標文本進行自動去重。只不過本文要介紹的“去重方法”與上…

計算機專業必考之計算機指令設計格式

計算機指令設計格式 例題&#xff1a; 1.設相對尋址的轉移指令占3個字節&#xff0c;第一字節為操作碼&#xff0c;第二&#xff0c;第三字節為相對偏移量&#xff0c; 數據在存儲器以低地址為字地址的存放方式。 每當CPU從存儲器取出一個字節時候&#xff0c;自動完成&…

正點原子[第二期]Linux之ARM(MX6U)裸機篇學習筆記-24.1,2 SPI驅動實驗-SPI協議介紹

前言&#xff1a; 本文是根據嗶哩嗶哩網站上“正點原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸機篇”視頻的學習筆記&#xff0c;在這里會記錄下正點原子 I.MX6ULL 開發板的配套視頻教程所作的實驗和學習筆記內容。本文大量引用了正點原子教學視頻和鏈接中的內容。…

計算機組成原理易混淆知識點總結(持續更新)

目錄 1.機器字長&#xff0c;存儲字長與指令字長 2.指令周期,機器周期,時鐘周期 3.CPI,IPS,MIPS 4.翻譯程序和匯編程序 5.計算機體系結構和計算機組成的區別和聯系 6.基準程序執行得越快說明機器的性能越好嗎? 1.機器字長&#xff0c;存儲字長與指令字長 不同的機器三者…

AI智能體|扣子Coze文生圖功能接入微信公眾號

大家好&#xff0c;我是無界生長。 AI智能體&#xff5c;扣子Coze文生圖功能接入微信公眾號本文分享了如何將Coze平臺的文生圖功能接入微信公眾號的詳細操作流程&#xff0c;包括創建圖像流、創建并配置Bot、設置提示詞和開場白、調試、發布等步驟。如果看完還沒學會的話&…

網頁圖片加載慢的求解指南

網頁/圖片加載慢的求解指南 一、前言與問題描述 今天剛換上華為的HUAWEI AX3 Pro New&#xff0c;連上WIFI后測速雖然比平時慢&#xff0c;但是也不算太離譜&#xff0c;如下圖所示&#xff1a; 估計讀者們有也和作者一樣&#xff0c;還沒意識到事情的嚴重性&#x1f601;。 …

08Django項目--用戶管理系統--查(前后端)

對應視頻鏈接點擊直達 TOC 一些朋友加我Q反饋&#xff0c;希望有每個階段的完整項目代碼&#xff0c;那從今天開始&#xff0c;我會上傳完整的項目代碼。 用戶管理&#xff0c;簡而言之就是用戶的增刪改查。 08項目點擊下載&#xff0c;可直接運行&#xff08;含數據庫&…

PHP框架 Laravel

現在因為公司需求&#xff0c;需要新開一個Laravel框架的項目&#xff0c;毫無疑問&#xff0c;我又被借調過去了&#xff0c;最近老是被借調&#xff0c;有點陰郁&#xff0c;不過反觀來看&#xff0c;這也是好事&#xff0c;又可以復習和鞏固一下自己的知識點&#xff0c;接下…

大數據開發面試題【Spark篇】

115、Spark的任務執行流程 driver和executor&#xff0c;結構式一主多從模式&#xff0c; driver&#xff1a;spark的驅動節點&#xff0c;用于執行spark任務中的main方法&#xff0c;負責實際代碼的執行工作&#xff1b;主要負責&#xff1a;將代碼邏輯轉換為任務、在executo…

編譯qt5.15.2(mac/windows)的mysql驅動(附帶編譯好的文件)

文章目錄 0 背景1 編譯過程2 福利 0 背景 因為需要連接到mysql數據庫&#xff0c;所以需要連mysql驅動。 1 編譯過程 1&#xff0c;打開文件/Users/mac/Qt5.14.2/5.14.2/Src/qtbase/src/plugins/sqldrivers/sqldrivers.pro&#xff0c;注釋掉QMAKE_USE mysql&#xff1b; 如…

國產【Jetson Xavier NX】——從裸機到深度學習開發環境配置

1、設置系統從固態硬盤啟動 英偉達官方NX出廠是直接將SD卡&#xff08;64/128G&#xff09;燒錄系統作為系統盤使用&#xff0c;國產NX出廠是將系統配置在8G內存中&#xff0c;在后續使用中需配置大量開發包&#xff0c;故將系統設置為從固態硬盤啟動。 參考鏈接 https://blo…