C++的繼承和多態

繼承和多態

  • 繼承
    • 繼承的權限
    • 繼承的子父類訪問
    • 派生類的默認成員函數
    • 菱形繼承(C++獨有)【了解】
    • 虛擬繼承
    • 什么是菱形繼承?菱形繼承的問題是什么?
    • 什么是菱形虛擬繼承?如何解決數據冗余和二義性的
    • 繼承和組合的區別?什么時候用繼承?什么時候用組合?
  • 多態
    • 構成多態的條件
    • 多態的原理
      • 虛函數表的打印
    • 虛函數的重寫
      • 虛函數重寫的第一個例外--- 析構函數的重寫
      • 虛函數重寫的第二個例外---協變
    • 重載、覆蓋(重寫)、隱藏(重定義)的對比
      • 重寫和隱藏的詳細解釋
  • 抽象類
  • C++11的override和final
    • final
    • override
  • 面試問題
    • inline函數可以是虛函數嗎?
    • 靜態成員可以是虛函數嗎?
    • 構造函數可以是虛函數嗎?
    • 析構函數可以是虛函數嗎?什么場景下析構函數是虛函數?
    • 對象訪問普通函數快還是虛函數更快?
    • 虛函數表是在什么階段生成的,存在哪的?
    • 什么是抽象類?抽象類的作用?

繼承

繼承的權限

在這里插入圖片描述

繼承的子父類訪問

在這里插入圖片描述

派生類的默認成員函數

在這里插入圖片描述

菱形繼承(C++獨有)【了解】

iostream就是菱形繼承 可以去查庫
在這里插入圖片描述

虛擬繼承

虛擬繼承可以解決菱形繼承的二義性和數據冗余的問題。如上面的繼承關系,在Student
Teacher的繼承Person時使用虛擬繼承,即可解決問題。需要注意的是,虛擬繼承不要在其他地
方去使用

當一個類從多個類中繼承時,這些類又共同繼承自另一個基類,這時可以使用虛擬繼承來確保基類的共享實例。具體來說,就是在派生類聲明中使用virtual關鍵字來繼承基類,這樣在進一步的派生中,基類的成員就會被共享,而不是重復復制。

例如,如果Father和Mother類都虛擬繼承自GrandParent類,那么當GrandSon類繼承FatherMother時,GrandSon對象中只會有一個GrandParent的實例,這樣就避免了數據冗余。同時,由于虛擬基類的引入,對于基類成員的訪問也變得明確,解決了二義性問題。

class GrandParent {
public:void sayHello() {std::cout << "Hello from GrandParent!" << std::endl;}
};class Father : virtual public GrandParent {
};class Mother : virtual public GrandParent {
};class GrandSon : public Father, public Mother {
};int main() {GrandSon son;son.sayHello(); // 輸出 "Hello from GrandParent!"return 0;
}

什么是菱形繼承?菱形繼承的問題是什么?

菱形繼承是一種多繼承的特殊情況,它涉及四個類形成一個菱形結構。

在菱形繼承中,存在一個基類,兩個派生類繼承這個基類,然后另一個類同時繼承這兩個派生類。這種繼承方式在類的層次結構圖中看起來像一個菱形,因此得名。

菱形繼承的主要問題是數據冗余和二義性。

由于最底層的派生類繼承了兩個基類,而這兩個基類又繼承了同一個基類,所以會造成最頂部基類的兩次調用。這會導致相同數據的重復存儲,即冗余性。更重要的是,當訪問某個繼承自基類的屬性或方法時,會產生歧義,因為不清楚應該訪問哪個派生類中的版本,這就是所謂的二義性。

總而言之,菱形繼承是多繼承中特有的一種復雜情況,在設計類的繼承關系時應謹慎使用,以避免引起數據冗余和二義性問題。

什么是菱形虛擬繼承?如何解決數據冗余和二義性的

菱形虛擬繼承是一種特殊的多繼承方式,它通過虛擬基類來解決菱形繼承中的數據冗余和二義性問題

在C++中,菱形虛擬繼承是通過使用關鍵字virtual來實現的。當一個類從多個類中繼承時,這些類又共同繼承自另一個基類,這時可以使用虛擬繼承來確保基類的共享實例。具體來說,就是在派生類聲明中使用virtual關鍵字來繼承基類,這樣在進一步的派生中,基類的成員就會被共享,而不是重復復制。

例如,如果FatherMother類都虛擬繼承自GrandParent類,那么當GrandSon類繼承FatherMother時,GrandSon對象中只會有一個GrandParent的實例,這樣就避免了數據冗余。同時,由于虛擬基類的引入,對于基類成員的訪問也變得明確,解決了二義性問題。

總的來說,雖然菱形虛擬繼承可以解決這些問題,但它也會增加代碼的復雜性。因此,在設計類的繼承結構時,應當謹慎考慮是否真的需要使用多繼承和虛擬繼承,以及它們帶來的復雜性和可能的性能影響。

繼承和組合的區別?什么時候用繼承?什么時候用組合?

下面以表格形式對比繼承和組合的區別以及它們的適用場景:

特性繼承組合
定義繼承是一種從現有類派生新類的關系。組合是指一個類包含另一個類的實例。
耦合性通常較高,因為子類與父類緊密相關。較低,因為類之間通過接口進行交互。
封裝性可能破壞封裝性,因為子類能訪問父類保護成員。維護良好的封裝性,只通過接口交互。
代碼重用允許子類重用父類的代碼和行為。通過聚合或包含實現代碼重用。
多態性支持,子類可以覆蓋或擴展父類方法。不直接支持,需要通過其他機制實現。
設計靈活性修改父類可能會影響所有子類。更靈活,整體與部分獨立變化。
使用場景適用于“是一個”關系(如貓是動物)。適用于“有一個”關系(如車有引擎)。
示例Dog繼承自MammalMammal繼承自AnimalCar包含Engine對象作為其組成部分。

何時使用繼承:

  • 當你想表達一種類型層級,例如,所有的貓都是哺乳動物,所有的哺乳動物都是動物。
  • 當子類需要父類的屬性和方法,并且可能還需要在子類中添加額外的特性或重寫父類的方法。

何時使用組合:

  • 當你想表達的是聚合關系,例如,一輛車有一個引擎,但車不是引擎的一種類型。
  • 當你希望保持類之間的松耦合,使得一個類的內部實現可以獨立于使用它的類而變化。

在實際的軟件開發中,組合通常被認為是比繼承更有優勢的設計選擇,因為它提供了更好的靈活性和封裝性。然而,在某些情況下,繼承仍然是合適的,特別是在表示自然的層次關系時。

多態

構成多態的條件

繼承中要構成多態還有兩個條件:

  1. 必須通過基類的指針或者引用調用虛函數
  2. 被調用的函數必須是虛函數,且派生類必須對基類的虛函數進行重寫

多態的原理

虛函數表的打印

class Base {
private:int _b=1;
public:Base():_b(10){++_b;}virtual void fun1() {cout << "Base::fun1" << endl;}virtual void fun2() {cout << "Base::fun2" << endl;}void fun3() {cout << "Base::fun3" << endl;}
};class Derive :public Base {
private:int _d = 2;
public:virtual void fun1() {cout << "Derive::fun1" << endl;}virtual void fun4() {cout << "Derive::fun4" << endl;}
};typedef void(*VF_PTR)();
void PrintVFTable(VF_PTR* table) {for (int i = 0;table[i] != nullptr;++i) {printf("[%d]:%p->", i, table[i]);VF_PTR f = table[i];f();}cout << endl;
}
int main() {Base b;Derive d;PrintVFTable((*(VF_PTR**)&b));PrintVFTable((*(VF_PTR**)&d));return 0;
}

在這里插入圖片描述

虛函數的重寫

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

虛函數重寫的第一個例外— 析構函數的重寫

基類與派生類析構函數的名字不同

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

虛函數重寫的第二個例外—協變

基類與派生類虛函數返回值類型不同

派生類重寫基類虛函數時,與基類虛函數返回值類型不同。即基類虛函數返回基類對象的指
針或者引用,派生類虛函數返回派生類對象的指針或者引用時,稱為協變。(了解)

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

在C++中,重載(Overload)、覆蓋(Override,也稱為重寫)和隱藏(Hide,也稱為重定義)是三種不同的函數關系。它們的區別可以通過下表進行總結:

概念作用域參數列表返回類型
重載同一作用域必須不同可相同也可不同
覆蓋/重寫派生類與基類之間相同必須相同(C++11起,返回類型也可以被協變)
隱藏不同作用域(如基類與派生類)可以相同,也可以不同無特定要求

具體解釋如下:

  1. 重載
  • 作用域:發生在同一作用域內,通常是同一個類中。
  • 參數列表:同名函數必須有不同的參數列表(參數類型、個數或順序至少有一項不同)。
  • 返回類型:可以相同,也可以不同。
  1. 覆蓋/重寫
  • 作用域:發生在基類與派生類之間。
  • 參數列表:派生類中的函數必須與基類中的虛函數有完全相同的參數列表。
  • 返回類型:從C++11開始,返回類型可以是相同的,或者是派生類類型的派生類(協變返回類型)。
  1. 隱藏
  • 作用域:發生在不同作用域,例如基類與派生類中的非虛函數。
  • 參數列表:同名函數的參數列表可以相同,也可以不同。
  • 返回類型:沒有特定的要求。

綜上所述,重載允許在同一作用域內有多種接受不同參數的同名函數;覆蓋/重寫是指派生類重新定義了基類的虛函數,通常用于實現多態;而隱藏則是當派生類中的函數與基類中的函數同名時,無論參數列表是否相同,基類中的函數都會被隱藏。理解這些概念對于編寫正確的C++面向對象程序至關重要。

重寫和隱藏的詳細解釋

重寫(Overriding):
當你在派生類中定義一個與基類中同名且函數簽名(包括參數類型和返回類型)完全相同的虛函數時,你實際上是在提供一個新的實現。當通過基類的指針或引用調用這個函數時,C++運行時將動態地(在程序運行時)決定執行基類的版本還是派生類的版本,這是多態的一個特征。

class Base {
public:virtual void doSomething() {cout << "Base's doSomething" << endl;}
};class Derived : public Base {
public:virtual void doSomething() override { // 重寫基類的方法cout << "Derived's doSomething" << endl;}
};

在這個例子中,Derived類重寫了Base類的虛函數doSomething

隱藏(Hiding):
當派生類定義了一個與基類中同名的成員函數,哪怕是參數個數或類型不同,或者不是虛函數,基類的那個成員函數在派生類的對象上就無法直接訪問了。這被稱為隱藏。這不是多態的表現,而是簡單的名字覆蓋,這種情況下不會有運行時的動態調用。

class Base {
public:void doSomething() {cout << "Base's doSomething" << endl;}
};class Derived : public Base {
public:void doSomething(int x) { // 隱藏了基類的doSomethingcout << "Derived's doSomething with int" << endl;}
};

在這個例子中,Derived類隱藏了Base類的doSomething函數。

總結一下:

  • 重寫是多態的一個體現,重寫必須涉及到虛函數,函數簽名必須相同,C++通過虛函數表來實現運行時的動態綁定。
  • 隱藏發生在派生類聲明了一個與基類同名的函數后(而不管簽名是否相同),此時通過派生類的對象將不能訪問到基類中被同名函數隱藏的成員,除非顯式指定作用域。隱藏不涉及虛函數或運行時的動態綁定。

抽象類

在虛函數的后面寫上 =0 ,則這個函數為純虛函數。**包含純虛函數的類叫做抽象類(也叫接口
類),抽象類不能實例化出對象。**派生類繼承后也不能實例化出對象,只有重寫純虛函數,派生
類才能實例化出對象。純虛函數規范了派生類必須重寫,另外純虛函數更體現出了接口繼承。

class Car
{
public:
virtual void Drive() = 0;
};
class Benz :public Car
{
public:virtual void Drive(){cout << "Benz-舒適" << endl;}
};
class BMW :public Car
{
public:virtual void Drive(){cout << "BMW-操控" << endl;}
};
void Test()
{
Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();
}

C++11的override和final

final

final:修飾虛函數,表示該虛函數不能再被重寫
我的理解是這個虛函數是父類特有的功能,不能被子類所繼承。

class Car
{
public:virtual void Drive() final {}
};
class Benz :public Car
{
public:virtual void Drive() {cout << "Benz-舒適" << endl;}//這里會報錯說不能繼承
};

override

override: 檢查派生類虛函數是否重寫了基類某個虛函數,如果沒有重寫編譯報錯。
我的理解是這功有點雞肋,就是你在子類繼承后面寫override,override會幫你檢查該函數是否是需要虛函數重寫。

面試問題

inline函數可以是虛函數嗎?

答:可以,不過編譯器就忽略inline屬性,這個函數就不再是inline,因為虛函數要放到虛表中去。

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

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

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

答:不能,因為對象中的虛函數表指針是在構造函數初始化列表階段才初始化的。

析構函數可以是虛函數嗎?什么場景下析構函數是虛函數?

答:可以,并且最好把基類的析構函數定義成虛函數。

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

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

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

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

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

答:參考(3.抽象類)。抽象類強制重寫了虛函數,另外抽象類體現出了接口繼承關系。

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

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

相關文章

揭秘Android Tombstone:崩潰位置的秘密研究-Crash Location

由于一些工作原因&#xff0c;最近對Android系統發生crash的Tombstone展開了一定的研究。 這里我談一下關于對于Android Libstagefright 整數溢出漏洞的crash Tombstone的研究。看一下在包含整數溢出功能的MP4文件從PC傳輸進Android的時候造成的Tombstone0_0。 1、研究頭部信…

雙通道 40V 160mΩ車規級高側電源開關帶診斷功能反向電池保護功能

概述 PC8916是雙通道、高功率具有集成NMOS功率FET的開關&#xff0c;以及電荷泵。該設備集成了高級 保護功能&#xff0c;例如負載電流限制&#xff0c;通過功率限制進行過載主動管理帶可配置閉鎖的超溫停機。全面診斷和高精度電流感應這些功能實現了對負載的智能控制。有源漏…

[C++] 統計程序耗時

一、簡介 使用clock()函數記錄程序開始、結束時間戳。然后將開始結束時間戳差除以CLOCKS_PER_SEC得到程序的耗用的時間&#xff08;秒數&#xff09;。 二、代碼示例 #include <iostream> #include <time.h> #include <math.h> int main(int, char **) {clo…

JetPack 5.1編譯mish_cuda

1.查看jetpack版本:sudo jtop 自帶的就有cuda11.4和cudnn8.X以及python3.8,我的cudnn就沒有是后期自己安裝的 2.安裝torch PyTorch for Jetson - Announcements - NVIDIA Developer Forums 選擇對應的cuda版本和torch版本,我下載的是:torch-2.1.0a0+41361538.nv23.06-cp…

ETL數據倉庫的使用方式

一、ETL的過程 在 ETL 過程中&#xff0c;數據從源系統中抽取&#xff08;Extract&#xff09;&#xff0c;經過各種轉換&#xff08;Transform&#xff09;操作&#xff0c;最后加載&#xff08;Load&#xff09;到目標數據倉庫中。以下是 ETL 數倉流程的基本步驟&#xff1a…

2024中國5G隨身WiFi十大品牌排行榜,20245G隨身口碑排行榜,5G隨身WiFi2024最新款!5G隨身WiFi推薦測評

【中國品牌網中國3C質量評測中心權威榜單聯合發布】 第一名&#xff1a;格行5G隨身WiFi&#xff1a; 優點&#xff1a;隨身WiFi行業的頭部和領跑品牌&#xff0c;15年專業物聯網行業經驗&#xff0c;格行在技術研發、產品創新和客戶服務方面具有很高的口碑&#xff0c;被業內…

通過一篇文章讓你了解數據結構和算法的重要性

通過一篇文章讓你了解數據結構和算法的重要性 前言一、 什么是數據結構&#xff1f;二、什么是算法&#xff1f;三、數據結構和算法的重要性在校園招聘的筆試中&#xff1a;在校園招聘的面試中&#xff1a;在未來的工作中&#xff1a; 四、如何學好數據結構和算法4.1 死磕代碼&…

基于React全棧Sora AI視頻案例展示項目

花了一天時間基于React Next全棧開發的Sora AI 演示項目 Preview: https://sora.langchat.cn/ Github&#xff1a;https://github.com/tycoding/lang-sora 歡迎大家star、fork呀&#xff01; 這是一套完整的React & Next.js項目&#xff0c;包含前后端交互、路由、數據庫…

crc16計算

crc16計算&#xff0c;以生成式G(x)x16x15x21,為例 1、函數如下&#xff1a; //crc&#xff1a;G(x) x16x15x21 #define POLY 0x8005 //對應的生成式的多項式&#xff0c;可以查&#xff08;在在線計算crc工具下查&#xff09; unsigned short crc16_2(unsigned char *da…

CBAM注意力機制詳解(附pytorch復現)

簡介 論文原址&#xff1a;1807.06521.pdf (arxiv.org) CBAM&#xff08;Convolutional Block Attention Module&#xff09;是一種卷積神經網絡模塊&#xff0c;旨在通過引入注意力機制來提升網絡的表示能力。CBAM包含兩個順序子模塊&#xff1a;通道注意力模塊和空間注意力…

算法項目的合作流程

算法項目的合作流程通常包括以下幾個關鍵步驟&#xff0c;以上是算法項目合作的基本流程&#xff0c;具體項目可能會根據實際情況進行調整和補充。在整個項目過程中&#xff0c;良好的溝通、協作和團隊合作至關重要&#xff0c;能夠確保項目按時高質量地完成。北京木奇移動技術…

回歸啦!!!

消失的日子在實習&#xff0c;今天最后一天了來看看自己的學習日志&#xff0c;有沒有可以和小伙伴交流的部分吧&#xff01; 目錄 一、產品one ①簡介 ②底層原理 ③知識點一 作用一&#xff1a;日志采集 作用二&#xff1a;實時監測 作用三&#xff1a;規則匹配 作用…

Redis沖沖沖——事務支持,AOF和RDB持久化

目錄 引出Redis事務支持&#xff0c;AOF和RDB持久化1、Redis的事務支持2、Redis的持久化 Redis沖沖沖——緩存三兄弟&#xff1a;緩存擊穿、穿透、雪崩緩存擊穿緩存穿透緩存雪崩 總結 引出 Redis沖沖沖——事務支持&#xff0c;AOF和RDB持久化 Redis事務支持&#xff0c;AOF和…

codeforces 1868A

題目鏈接 思路 當 m 1 m1 m1時 發現是 M M M是一條 0 0 0的縱列&#xff0c;最后結果是 0 0 0 其余構造方法大體為&#xff1a;每行把上一行第一位元素移到隊尾 當 n < m ? 1 n<m-1 n<m?1時 我們可以如下構造 0,1,2,3,4…m-1 1,2,3,4…m-1,0 2,3,4…m-1,0,1…

【內部消息】24上半年軟考可能支持平板、PC和手機等多平臺報名

根據內部消息&#xff0c;軟考網上報名系統正在改革&#xff0c;之前只能通過PC端報名的&#xff0c;下次報名可能支持平板、手機等多終端進行網上報名了。現在官方并沒有確切消息發出&#xff0c;這次變動可能發生在2024上半年&#xff0c;也有可能得到下半年才能實行。以下是…

一文讀懂MES之工藝路線

什么是工藝路線 工藝路線&#xff0c;又被稱為生產工藝流程或生產流程路線&#xff0c;是指在進行產品或零件的生產過程中&#xff0c;按照一定的生產順序排列的一系列的工藝過程。簡單來說就是如何從原材料或者半成品零件&#xff0c;一步一步加工和制作&#xff0c;最終制作…

LeetCode_Java_動態規劃系列(2)(題目+思路+代碼)

131.分割回文串 給你一個字符串 s&#xff0c;請你將 s 分割成一些子串&#xff0c;使每個子串都是 回文串 。返回 s 所有可能的分割方案。 回文串 是正著讀和反著讀都一樣的字符串。 示例 1&#xff1a; 輸入&#xff1a;s "aab" 輸出&#xff1a;[["a&qu…

InnoDB索引與優化篇(3)-事務隔離級別與InnoDB的應用

MySQL是一種常用的關系型數據庫管理系統&#xff0c;而事務是數據庫中常用的一種機制。在MySQL中&#xff0c;事務的隔離級別以及使用InnoDB引擎進行事務處理是非常重要的。在本博客中&#xff0c;我們將探討MySQL數據庫事務隔離級別和InnoDB的應用。 事務是一組數據庫操作的集…

立即報名Atlassian Team’24,與龍智一同踏上前往數字服務的創新之路

拉斯維加斯&#xff0c;4月30日至5月2日—— Atlassian Team’24盛大舉行&#xff01;現已正式啟動報名&#xff0c;誠邀您的參與&#xff01;與龍智一同走進這場創新與協作的盛會&#xff0c;您將有機會親身感受100余場精彩紛呈的活動&#xff0c;深入探索Atlassian平臺如何助…

小程序常用樣式和組件

常用樣式和組件 1. 組件和樣式介紹 在開 Web 網站的時候&#xff1a; 頁面的結構由 HTML 進行編寫&#xff0c;例如&#xff1a;經常會用到 div、p、 span、img、a 等標簽 頁面的樣式由 CSS 進行編寫&#xff0c;例如&#xff1a;經常會采用 .class 、#id 、element 等選擇器…