C++筆記-繼承(下)(包含派生類的默認成員函數,菱形繼承等)

一.派生類的默認成員函數

1.14個常見默認成員函數

?默認成員函數,默認的意思就是指我們不寫,編譯器會自動為我們生成一個,那么在派生類中,這幾個成員函數是如何生成的呢?

1.派生類的構造函數必須調用基類的構造函數初始化基類的那一部分成員。如果基類沒有默認的構造函數,則必須在派生類構造函數的初始化列表顯示調用。

2.派生類的拷貝構造函數必須調用基類的拷貝構造完成基類的拷貝初始化。

3.派生類的operator=必須要調用基類的operator=完成基類的復制。需要注意的是派生類的operator=隱藏了基類的operator=,所以顯示調用基類的operator=,需要指定基類作用域
4.派生類的析構函數會在被調用完成后自動調用基類的析構函數清理基類成員。因為這樣才能保證派生類對象先清理派生類成員再清理基類成員的順序。
5.派生類對象初始化先調用基類構造再調派生類構造。
6.派生類對象析構清理先調用派生類析構再調基類的析構。
7.因為多態中一些場景析構函數需要構成重寫,重寫的條件之一是函數名相同。那么編譯器會對析構函數名進行特殊處理,處理成destructor(),所以基類析構函數不加 virtual的情況下,派生類析構函數和基類析構函數構成隱藏關系。

首先先寫一個父類Person,便于下面的講解。

在寫子類的4個默認函數時要把父類成員當成一個整體

首先將其中的構造函數:

正如第一條所言,我們在寫子類的構造函數時,在初始化列表部分要顯示調用父類的構造函數,這里顯式調用的格式就如上圖所示,如果父類還有其他的成員變量,那么一并都寫入父類的構造函數種。

接下來呢時拷貝構造函數,這類有人可能有疑惑:為什么直接傳s就可以呢?
這個就涉及到我們上一篇講的基類和派生類之間的轉化,有印象的朋友就能記起來子類對象可以賦值給父類的引用或指針,本質就是“切片”,而我們上面寫的Person類中的拷貝構造函數正是引用,所以這里直接傳子類對象即可。

接著是=符號重載,這里面要注意的是我們要指定作用域來調用Person類中的=符號重載,至于原因也和上一篇講的知識有關:最后講的隱藏規則。

這里如果不指定作用域,那么子類的=符號重載會將父類的=符號重載給隱藏,這里會一直調用自己,一直遞歸,最終導致棧溢出。

而最后的析構函數呢情況比較復雜,就如上圖所示,按理說應該沒問題的,就和上面的構造函數和拷貝構造函數一樣,那么這里為何為報錯呢?

原因就正如第七條所言,因為都是destructor,所以子類的析構函數把父類的析構函數給隱藏了,所以這里就找不到父類的析構函數。

所以要利用父類的析構函數就要指定作用域,但是析構函數比較特殊,我們在實際應用中不會去顯示調用父類的析構函數,原因就如第六條所言,我們要調用子類的析構函數,再調用父類的析構函數,而這里我們如果在子類析構函數中上去就直接調用父類的析構函數,那么就會使順序顛倒。

并且因為第四條所言,那么就會導致父類的析構函數被調用兩次:

很明顯,是不能這么用的,這肯定會出問題的。所以在實際運用中我們是不需要主動去調用父類的析構函數,這也是析構函數和前面三個默認函數的區別。

4.2實現一個不能被繼承的類

不能被繼承的方式我講解兩種:c++98和c++11兩種不同的方式。

c++98的方法就是將父類的構造函數放在private下,這種方式為什么可以呢?

就如上面講的構造函數,子類構造函數要顯示調用父類的構造函數,而父類的構造函數不能訪問,通過這種機制就導致父類無法被繼承。

當我們創建對象時就會出現這樣的報錯。

而c++11的方法相較于c++98簡單了許多,直接在類名后面加一個關鍵字final,這樣這個類就無法被繼承了,就如上圖所示。

二.繼承和友元

友元關系不能被繼承,也就是說基類友元不能訪問派生類私有和保護成員。

我們以上面的例子為例,在講解之前,注意我上面寫的前置聲明,因為Display函數同時用到了兩個類,而student類還沒有實現,所以就在前面加上一個前置聲明,來告訴編譯器我下面有一個類叫student。

通過上面的例子可以看出,Display并不能訪問子類的保護成員。這就像你父親的朋友不是你的朋友一樣,是一個道理,所以是無法訪問的。

而要想訪問呢就在子類中進行友元聲明即可訪問。

三.繼承與靜態成員

基類定義了startic靜態成員,則整個繼承體系里面只有一個這樣的成員。無論派生出多少個派生類,都只有一個static成員實例。

可以看出如果是非靜態成員,父類和子類_name地址是不一樣的,說明派生類繼承下來了,父類和派生類對象各有一份。

而靜態成員通過檢驗可以看出,父類和子類_count的地址是一樣的,說明父類和子類共用同一份靜態變量。

四.多繼承及菱形繼承問題

4.1繼承模型

單繼承:一個派生類只有一個直接基類時稱這個繼承關系為單繼承
多繼承:一個派生類有兩個或以上直接基類時稱這個繼承關系為多繼承,多繼承對象在內存中的模型是,先繼承的基類在前面,后面繼承的基類在后面,派生類成員在放到最后面。
菱形繼承:菱形繼承是多繼承的一種特殊情況。菱形繼承的問題,從下面的對象成員模型構造,可以看出菱形繼承有數據冗余二義性的問題。支持多繼承就一定會有菱形繼承,像Java就直接不支持多繼承,規避掉了這里的問題,所以實踐中我們也是不建議設計出菱形繼承這樣的模型的。

上面就是單繼承的示意圖,下面就是多繼承的示意圖,也是菱形繼承的示意圖。

嚴格的多繼承第二張圖去掉上面的Person,這樣就成了一個標準的多繼承。

單繼承和多繼承比較簡單,主要來講菱形繼承的問題:

1.數據冗余

以第二幅圖為例,如果Person中有一個_name的成員變量,那么student中會有一份name,teacher中也會有一份,這就導致Assistant在同時繼承student和teacher時,就會有兩份name,這就是數據冗余,其實也就是造成空間的浪費。

我們都知道沒必要存兩份name,但是菱形繼承就會導致這個問題。

而數據冗余又會引出二義性的問題。

2.二義性

此時創建一個Assistant對象,訪問name就會報錯,因為編譯器不知道到底要訪問student中的name還是teacher中的name,這就是二義性。

而要解決這種問題有兩種辦法:

第一種辦法就是指定作用域,指定你要訪問哪個作用域的name,可以解決問題。

4.2虛繼承

很多人說c++語法復雜,其實多繼承就是一個體現。有了多繼承,就存在菱形繼承,有了菱形繼承就有菱形虛擬繼承,底層實現就很復雜,性能也會有一些損失,所以最好不要設計出菱形繼承。多繼承可以認為是c++的缺陷之一,后來的一些編程語言都沒有多繼承,如java。

而虛繼承就是上面的第二種解決方式:

此時我們不指定作用域也不會報錯,因為虛繼承,顧名思義就是看似繼承了,其實沒有繼承,到最后Assistant中只有一份name。

使用虛繼承后構造函數就要引用Person類的構造函數,因為是虛繼承,student和teacher類中的構造函數都會調用Person類的構造函數,你傳參過去編譯器不知道到底用誰的name。

并且顯式調用Person類的構造函數時,其實編譯器并不會走student和teacher類中Person類構造函數那一行,也就是說寫了之后只會走Assistant類中顯式調用的Person類構造函數,不走其他兩個的,即使顯式調用了。

這也就是虛繼承很燃的地方,比較繞,這也是因為多繼承引出的問題而填的坑。

我們通過調試也可以發現,當修改了name之后,所有的name都會發生改變,也說明了name只有一份,看似student和teacher中有name,其實根本就沒有把name放入進去。

虛繼承呢大家也不必深究,因為實際操作中我們也避免生成菱形繼承,并且虛繼承也會造成性能損失,所以了解一下即可。

最后我們再看一道題:

問:p1,p2和p3的關系?

A.p1==p2==p3? ?B.p1<p2<p3? ?C.p1==p3!=p2? ?D.p1!=p2!=p3

大家可以思考一下這個問題。

答案呢選c,這個題呢涉及到了多繼承中的指針偏移問題:

通過這個圖就可以清晰觀察到創建對象時指針的位置,p3==p1完全是巧合,正好都指向這段空間的起始位置,而p2就發生了指針偏移,經過切片后,base2基類的地址在中間的,因為前面是base1基類的地址,所以沒有指向起始位置。

大家思考一下這個是菱形繼承嗎?

答案是菱形繼承,在多繼承的情況下只要有公共的基類,就是菱形繼承,這個怎么體現呢?

B繼承了A,C繼承了B,那么C中就有一份A,D繼承了A,那么D中也有一份A,所以是菱形繼承。

再思考一下,如果要加虛繼承那么加在哪兩個位置呢?

答案加在BD兩處,因為BD兩處是直接導致有兩份A的地方,所以是BD兩處加。

5.繼承和組合

1.public繼承是一種is-a的關系。也就是說每個派生類對象都是一個基類對象。組合是一種has-a的關系。假設B組合了A,每個B對象中都有一個A對象。
2.繼承允許你根據基類的實現來定義派生類的實現。這種通過生成派生類的復用通常被稱為白箱復用(white-box reuse)。術語“白箱”是相對可視性而言:在繼承方式中,基類的內部細節對派生類可見。繼承一定程度破壞了基類的封裝,基類的改變,對派生類有很大的影響。派生類和基類間的依賴關系很強,耦合度高。
3.對象組合是類繼承之外的另一種復用選擇。新的更復雜的功能可以通過組裝或組合對象來獲得。對象組合要求被組合的對象具有良好定義的接口。這種復用風格被稱為黑箱復用(black-box reuse),因為對象的內部細節是不可見的。對象只以“黑箱”的形式出現。組合類之間沒有很強的依賴關系,耦合度低。優先使用對象組合有助于你保持每個類被封裝。
4.優先使用組合,而不是繼承。實際盡量多去用組合,組合的耦合度低,代碼維護性好。不過也不太那么絕對,類之間的關系就適合繼承(is-a)那就用繼承,另外要實現多態,也必須要繼承。類之間的關系既適合用繼承(is-a)也適合組合(has-a),就用組合。

組合的大概形式就如上圖所示。

這也是個概念性的知識,大家看上面的解釋,知道基本形式怎么用就行。

以上就是c++繼承(下)的內容。

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

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

相關文章

C++中指針使用詳解(3)數組、指針和函數參數傳遞的底層 ABI實現

要深入理解 數組、指針和函數參數傳遞 的底層 ABI&#xff08;Application Binary Interface&#xff09;實現&#xff0c;需要從以下幾個維度出發進行學習&#xff1a; 一、什么是 ABI&#xff1f; ABI 是編譯器和操作系統之間的協定&#xff0c;規定了&#xff1a; 函數如何…

【RustDesk 】中繼1:壓力測試 Python 版 RustDesk 中繼服務器

測試 Python 版 RustDesk 中繼服務器 測試我們實現的中繼服務器有幾種方法,從簡單到復雜依次如下: 1. 基本連接測試客戶端 創建一個簡單的測試客戶端來驗證中繼服務器的基本功能: 2. 用兩個測試客戶端測試中繼功能 要測試完整的中繼功能,你需要運行兩個客戶端實例來模擬…

Spring Boot集成Spring Cloud 2024(不使用Feign)

本文介紹Spring Boot集成Spring Cloud 2024&#xff0c;且不使用Feign&#xff0c;而是采用Spring 6自帶的HttpExchange方式進行服務調用的詳細步驟&#xff1a; 環境準備 Spring Boot版本&#xff1a;推薦使用Spring Boot 3.4.1及以上版本&#xff0c;以更好地與Spring Clou…

vue中$set原理

Vue 中的 $set 方法&#xff08;Vue.set&#xff09;主要用于 向響應式對象中添加一個新的屬性&#xff0c;并確保這個新屬性是響應式的&#xff0c;能夠觸發視圖更新。 &#x1f4cc; 背景問題&#xff1a;為什么需要 $set&#xff1f; 在 Vue 2 中&#xff0c;直接給對象新增…

Superset二次開發之深度解讀系列:1.概述

Apache Superset 是一款現代化的企業級商業智能 Web 應用程序&#xff0c;專為數據探索和可視化而設計。本概述介紹了 Superset 的架構、核心組件和主要功能&#xff0c;以幫助開發人員了解該系統的工作原理。 What is Apache Superset? Apache Superset 是一個開源數據探索…

Linux系統之elfedit詳解

elfedit 是一個用于修改 ELF&#xff08;可執行與可鏈接格式&#xff09;文件頭的工具。它允許用戶根據指定的條件&#xff08;如機器類型、文件類型、操作系統/ABI&#xff09;匹配并更新 ELF 文件的頭部信息。支持 32 位和 64 位 ELF 文件&#xff0c;以及包含 ELF 文件的歸檔…

前端HTML基礎知識

1.HTML介紹 HTML(HyperText Markup Language&#xff0c;超文本標記語言)是構成網頁的基本元素&#xff0c;是一種用于創建網頁的標準化標記語言。HTML不是一種編程語言&#xff0c;而是一種標記語言&#xff0c;通過標簽來描述網頁的結構和內容。 超文本&#xff1a;超文本是…

【IP101】圖像濾波技術詳解:從均值濾波到高斯濾波的完整指南

&#x1f31f; 圖像濾波魔法指南 &#x1f3a8; 在圖像處理的世界里&#xff0c;濾波就像是給圖片"美顏"的魔法工具。讓我們一起來探索這些神奇的濾波術吧&#xff01; &#x1f4d1; 目錄 1. 均值濾波&#xff1a;圖像的"磨皮"大法2. 中值濾波&#xff1…

LINE FRIENDS 正式與 Walrus 合作,全新 AI 驅動的游戲即將上線

風靡全球的 LINE FRIENDS 角色即將以“minini”迷你造型登陸 Walrus&#xff0c;雖然尺寸更小&#xff0c;但承諾帶來“大”動作。IPX&#xff08;LINE FRIENDS 背后的公司&#xff09;打造了《minini universe: ROOM》游戲&#xff0c;這是一款基于其 minini 系列角色的多鏈游…

2025年信息素養大賽C++算法創意實踐挑戰賽初賽樣題及答案解析(小學組)

一、選擇題 1、下列代碼&#xff0c;能夠輸出 hello world 的是_____ A. cout (hello world) B. cout << hello world C. cout:hello world D. cout << "hello world"; 答案&#xff1a;D 解析&#xff1a;cout輸出的文本內容要用雙引號引起來 2、…

[c語言日寄]檢查環形鏈表

【作者主頁】siy2333 【專欄介紹】?c語言日寄?&#xff1a;這是一個專注于C語言刷題的專欄&#xff0c;精選題目&#xff0c;搭配詳細題解、拓展算法。從基礎語法到復雜算法&#xff0c;題目涉及的知識點全面覆蓋&#xff0c;助力你系統提升。無論你是初學者&#xff0c;還是…

黃雀在后:外賣大戰新變局,淘寶+餓了么開啟電商大零售時代

當所有人以為美團和京東的“口水戰”硝煙漸散&#xff0c;外賣大戰告一段落時&#xff0c;“螳螂捕蟬&#xff0c;黃雀在后”&#xff0c;淘寶閃購聯合餓了么“閃現”外賣戰場&#xff0c;外賣烽火再度燃起。 4 月30日&#xff0c;淘寶天貓旗下即時零售業務“小時達”正式升級…

如何在uni-app中自定義輸入框placeholder的樣式

在開發uni-app應用時&#xff0c;我們經常需要自定義輸入框&#xff08;<input>&#xff09;的樣式以匹配應用的整體設計。默認情況下&#xff0c;uni-app的輸入框提供了一些基本的樣式選項&#xff0c;但有時候我們需要更細致地控制輸入框的每個部分&#xff0c;例如pla…

使用Node編寫輕量級后端快速入門

使用Node編寫輕量級后端快速入門 node 要作為輕量級后端需要下載一些對應模塊可以參考下面命令。你可以借助 npm&#xff08;Node Package Manager&#xff09;來下載它們。 模塊下載 express&#xff1a;這是一個廣受歡迎的 Node.js Web 應用框架&#xff0c;能用于構建 Web…

從Markdown到專業文檔:如何用Python打造高效格式轉換工具

在技術寫作、學術研究和企業報告領域,Markdown因其簡潔高效的特性廣受開發者喜愛。但當需要輸出正式文檔時,Word和PDF格式仍是行業標準。傳統解決方案往往存在樣式丟失、代碼排版混亂、批量處理困難等痛點。本文將揭秘如何用Python構建一個支持多主題、保留代碼高亮、自動生成…

【docker學習筆記】如何刪除鏡像啟動默認命令

一些鏡像會在它打鏡像時&#xff0c;加入一些默認的啟動命令&#xff0c;可以通過docker inspect \<image id\>來查看Entrypoint。如下圖&#xff0c;docker run啟動時&#xff0c;會默認執行 "python3 -m vllm.entrypoints.openai.api_server" 如果不想執行&…

任意無人機手柄鏈接Unity-100元的鳳凰SM600手柄接入Unity Input System?

網上教程真少&#xff01;奮發圖強自力更生&#xff01;2025.5.1 目前有用的鏈接&#xff1a; unity如何添加自定義HID設備&#xff0c;自己開發的手柄如何支持unity。 - 嗶哩嗶哩 HID Support | Input System | 1.0.2 官方教程 https://zhuanlan.zhihu.com/p/503209742 分…

2024睿抗CAIP-編程技能賽-本科組(省賽)題解

藍橋杯拿了個省三&#xff0c;天梯沒進1隊&#xff0c;睿抗是我最后的機會 RC-u4 章魚圖的判斷 題目描述 對于無向圖 G ( V , E ) G(V,E) G(V,E)&#xff0c;我們定義章魚圖為&#xff1a; 有且僅有一個簡單環&#xff08;即沒有重復頂點的環&#xff09;&#xff0c;且所…

Java 泛型參數問題:‘ResponseData.this‘ cannot be referenced from a static contex

問題與處理策略 問題描述 Data AllArgsConstructor NoArgsConstructor public class ResponseData<T> {private Integer code;private String msg;private T data;public static final int CODE_SUCCESS 2001;public static final int CODE_FAIL 3001;public static …

用TCP實現服務器與客戶端的交互

目錄 一、TCP的特點 二、API介紹 1.ServerSocket 2.Socket 三、實現服務器 四、實現客戶端 五、測試解決bug 1.客戶端發送了數據之后&#xff0c;并沒有響應 2.clientSocket沒有執行close()操作 3.嘗試使用多個客戶端同時連接服務器 六、優化 1.短時間有大量客戶端訪…