【C++】繼承----下篇

文章目錄

  • 前言
  • 一、實現一個不能繼承的類
  • 二、友元與繼承
  • 三、繼承與靜態成員
  • 四、多繼承以及菱形繼承問題
    • 1.繼承模型:
    • 2.菱形繼承的問題
    • 3.虛擬繼承解決數據冗余和二義性的原理
    • 4.虛擬繼承的原理
  • 五、繼承的總結和反思
    • 1.繼承和組合
  • 總結


前言

各位好呀!今天呢我們接著講繼承的相關知識,之前給大家已經分享了繼承一部分知識。那今天小編就來給繼承收個尾吧。來看看繼承的剩下的一部分。
在這里插入圖片描述


一、實現一個不能繼承的類

想要實現一個不能被繼承的類的呢有兩種方法:

  1. 方法一:父類的構造函數私有,子類的構造必須調用父類的構造函數,但是父類的構造函數私有化以后呢,在子類中是不可見也不可調用的。那么 子類就無法實例化出對象,這樣就可以達到父類不能被繼承(C++98的方法)。
  2. 方法二:C++11新增了一個關鍵字final,用final修飾父類,那么子類就無法繼承父類了
#include<iostream>
#include<string>
using namespace std;
class Person//C++11
{
public:
protected:string _name; 
private:Person()//私有化構造函數{}
};
class student:public Person  
{
public:
private:string ID;
};
int main()
{student s;//這里會報錯的,因為構造函數已經被私有化,子類是調不到父類的構造函數的//但是這里要注意的是:如果我們這里不定義,代碼是不會報錯的。return 0;
}#include<iostream>
#include<string>
using namespace std;
class Person final//C++11
{
public:Person()//私有化構造函數 {} 
protected:string _name; 
private:};
class student:public Person  //像這樣用final修飾父類的話,父類也不能被子類繼承 
{
public:
private:string ID;
};
int main()
{student s;return 0;
}

二、友元與繼承

注意:友元關系不能被繼承,也就是說,父類的友元不能訪問子類的私有成員和保護成員。

解決方法在子類中加上友元就可以了。還有就是要注意一下需要前置聲明一下子類

#include<iostream>
#include<string>
using namespace std;
class student;  //前置聲明
class Person
{friend void  Print(const Person& p, const student& s); //編譯器在遇到一個變量和函數的時候,都只會向上查找(提高查找的效率),// 所以這里的student就會向上查找,但是上面沒有student,student在下面//還有就是student不能放在方面取,因為student要繼承Person。這兩者相互依賴。//為了解決這個問題呢我們會在上面加一個前置聲明
public:
protected:string _name="帥哥"; 
};
class student:public Person 
{friend void  Print(const Person& p, const student& s); //由于繼承關系不能被繼承下來,所以就訪問不到student中_num成員變量//解決這個問只需要像這樣,加一個友元就可以解決這個問題了。
public:
protected :string _num="123456";
};
void Print(const Person& p, const student& s)
{cout << p._name << endl;  cout << s._num << endl; 
}
int main()
{student v;Person  c;Print(c,v);    return 0;
}

三、繼承與靜態成員

#include<iostream>
#include<string>
using namespace std; 
class Person
{
public:string _name; static int n;       
}; 
int Person::n = 1; 
class student :public Person
{
public:string  _num;
};
int main()
{Person p;student s;//非靜態成員變量的地址 cout << &p._name << endl;cout << &s._name << endl; cout << endl; //靜態成員變量的地址cout << &p.n  << endl;cout << &s.n << endl; return 0;
}

在這里插入圖片描述

我們通過看到非靜態成員_name地址是不一樣,這說明了子類繼承下來的成員在子類和父類中各有一份。但是靜態成員是不是地址相同呀?這又說明父類定義了static靜態成員,則整個繼承體系里面只有一個這樣的成員。無論派生出多少個子類,都只有一個static成員實例。
還有一個就是在共有的情況下,父類和子類指定作用域就可以訪問靜態成員。這里突破作用域就可以訪問,因為它沒有存在對象里面,而是存在靜態區,它只是受到類域的限制而已。還有就可以把靜態成員理解成全局的。

四、多繼承以及菱形繼承問題

1.繼承模型:

繼承模型呢分為三種:單繼承,多繼承和菱形繼承

  1. 單繼承一個子類只有一個直接父類時稱這種繼承關系為單繼承
    在這里插入圖片描述
  2. 多繼承一個子類有兩個或以上直接父類時稱這個繼承關系為多繼承
    在這里插入圖片描述
  3. 菱形繼承菱形繼承屬于一個特殊的多繼承

在這里插入圖片描述

2.菱形繼承的問題

菱形繼承主要時兩個問題,分別是 二義性和數據冗余

二義性
首先,什么是二義性?二義性就是在訪問數據的時候發生歧義,不知道訪問那個。

示例:

#include<iostream>
#include<string>
using namespace std; 
class A
{
public:string _name;int _age;
};
class B:public A 
{
public:
protected:string  _number;
};
class C :public A
{
public:
protected:string Gender;  
};
class D :public B, public C
{
public:
protected:string ID;
};
int main()
{D d;d._name;//存在二義性 
}

在這里插入圖片描述
問題分析
這里為什么會存在訪問不明確呢?結合之前的知識,子類繼承父類成員,那么子類和父類中是不是都分別有一份獨立的成員。但是現在B和C這兩個類都繼承了A,然而D又繼承B和C。也就是說A,B,C,D中分別都有一份_name,現在要訪問父類中的成員_name,但是這里的D是繼承了兩個類,兩個類中都有_name ,所以這里編譯器就不知道該訪問那個類里面的_name這就是二義性。

解決方法:
怎樣解決這個問題呢?其實很簡單,只需要顯示指定訪問那個父類中_name就可以解決問題,但是不能解決數據冗余的問題

#include<iostream>
#include<string>
using namespace std; 
class A
{
public:string _name;int _age;
};
class B:public A 
{
public:
protected:string  _number;
};
class C :public A
{
public:
protected:string Gender;  
};
class D :public B, public C
{
public:
protected:string ID;
};
int main()
{D d;d.B::_name="張三";//指定要訪問的父類d.C::_name = "小張"; //指定要訪問的父類
}

數據冗余:
數據冗余可以理解成數據重復造成空間的浪費

示例:
菱形繼承還有個特別煩的點就是他會讓空間變大,一個它的父類在被幾個類繼承時,那它就有幾份。比如下列代碼中,A在B,C中各有一份。

#include<iostream>
#include<string>
using namespace std; 
class A
{
public:string _name; int _age; 
};
class B:public A 
{
public:
protected:string  _number;
};
class C :public A
{
public:
protected: string Gender;  
};
class D :public B, public C
{
public:
protected:string ID;
};
class F:public A,public B
{
public:};
int main()
{D d;F f;cout << sizeof(d) << endl;//菱形繼承大小cout << sizeof(f) << endl;//多繼承的大小
}

在這里插入圖片描述
可以看出兩者的空間大小相差的將近一倍了。所以,一般不建議創建菱形繼承,因為這樣有太多的問題,有時候還把握不住,建議不使用。

3.虛擬繼承解決數據冗余和二義性的原理

菱形繼承一般不建議使用,但是如果非要使用,那該怎樣解決二義性和數據冗余呢?這里就要引用一個新的關鍵字 virtual(虛擬繼承)

那這個關鍵字該怎么用呢?該加在哪里呢?先看示例:

#include<iostream>
#include<string>
using namespace std;
class A
{
public:string _name;int _age;
};
class B :virtual public A
{
public:
protected:string  _number;
};
class C :virtual public A
{
public:
protected:string Gender;
};
class D :public B, public C
{
public:
protected:string ID;
};
int main()
{D d;d._name = "張三";return 0;
}

通過上面的代碼可以看出:
virtual應該加在產生二義性和數據冗余繼承的地方,現在A是不是產生了二義性和數據冗余 ,那virtual就加在B和C繼承的哪里。這樣就解決了二義性和數據冗余的問題,這點我們可以通過監視窗口可以看出。
在這里插入圖片描述
從監視窗口展示的原因,這里雖然看起來是三份,但其實是一份。這樣是不是就解決了數據冗余和二義性的問題啊。
注意:這里的virtual不能只加在B或者C,必須要同時加在B和C

在這里插入圖片描述
大家看看這上圖,圖中的關系是不是菱形繼承呢?其實 上圖也時菱形繼承哦,大家不要對形狀太刻板了哦,認為菱形繼承那他的形狀就必須時菱形。形狀不是菱形但是有二義性和數據冗余的產生那他就是菱形繼承,

思考以及解決方法
那這里的virtual該加在哪里呢?BC?還是DC?其實正確是應該是加在BC,這里是不是A產生二義性和數據冗余,那就要加BC呀!那這里可以不可以在BCD都加上virtual呢?這里就好比一個人只需要兩根拐棍,而你偏要給他三根是一樣的性質。在D那里都沒有產生二義性和數據冗余那就沒必要加。
在這里插入圖片描述
還有就是大家在寫代碼的時候遇到上圖這種繼承的時候都加上virtual,這種情況呢屬于過度防范了。首先我們可以先看看B和C有沒有被同一個類繼承。如果沒有,可以先不用加;如果有,那再加上virtual是不是也不遲啊?所以大家在寫代碼的時候不要過度的防范二義性和數據冗余。

4.虛擬繼承的原理

#include<iostream>
#include<string>
using namespace std;
class A
{
public:A(const char*name,int age=18):_name(name),_age(age) {}string _name;int _age;
};
class B :virtual public A
{
public:B(const char*name,const char*number="1234567899"):A(name),_number(number) {}
protected:string  _number;
};
class C :virtual public A
{
public:C(const char* name, const char* gender="男"):A(name), Gender(gender) {}
protected:string Gender;
};
class D :public B, public C 
{
public:D(const char*name,const char*id="1263457"):B(name) ,C(name) //,A(naem)//這里必須顯示調用,不然會報錯,//還有就是這里初始化怎么多name,那到底以誰的為準呢?,ID(id) {}
protected:string ID;
};
int main()
{D d("張三"); return 0;
}

在這里插入圖片描述
這里從我們之前學的知識來看這里代碼的邏輯應該時沒有 問題的呀。調用子類的構造函數先調用父類的默認構造函數嘛。但是這里說class A 不存在默認構造。那是怎么回事呢?這不得不就要看看虛擬繼承的原理了。
在這里插入圖片描述
在這里插入圖片描述

相比于普通的多繼承,虛擬繼承呢是要把class A拿出來放在最底下的一個類中。他就不像普通多繼承那樣class A分別存在class B 和class C中。因為他要解決二義性和數據冗余。與此同時,這里還要引入一個虛基表和虛基表指針,復雜的很,所以小編這里就沒有展示出來。小編這里主要是像讓大家看看這兩種繼承的有什么不同。回到上面的問題:由于這里A不在B和C里面了,而是一個單獨的父類,所以A也因該顯示調用。

總結:不要輕易使用菱形繼承和寫出菱形繼承的代碼,多繼承可以用

五、繼承的總結和反思

1. 很多人說C++語法復雜,其實多繼承就是一個體現。有了多繼承,就存在菱形繼承,有了菱形繼承就有菱形虛擬繼承,底層實現就很復雜所以一般不建議設計出多繼承,一定不要設計出菱形繼承。否則在復雜度及性能上都有問題。

  1. 多繼承可以認為是C++的缺陷之一,很多后來的OO語言都沒有多繼承,如Java。

1.繼承和組合

  1. public繼承是一種is-a的關系。也就是說每個派生類對象都是一個基類對象。
  2. 組合是一種has-a的關系,。假設B組合了A,每個B對象中都有一個A對象。
    示例:我們以實現一個簡單的棧來演示:
//stack和cin構成has-a的關系
#include<iostream>
#include<string>
#include<stdbool.h> 
using namespace std;
class stack
{
public :void push(const int& x){cin.push_back(x);}void pop(){cin.pop_back();}const int& top()const {return cin.back();}bool empty(){return cin.empty();}
private:  vector<int >  cin; 
};
int main()
{stack s;s.push(1);s.push(2);s.push(3);while (!s.empty()){cout << s.top() << " ";s.pop();}return 0;
}//stack和vector是is-a關系
#include<iostream>
#include<stdbool.h> 
#include<vector>
using namespace std;
class stack:public std::vector<int>  
{
public:void push(const int& x){vector<int> ::push_back(x);}void pop(){vector<int> ::pop_back();}const int& top()const{return vector<int> ::back();}bool empty(){return vector<int> ::empty(); }
};
int main()
{stack s; s.push(1); s.push(2);s.push(3); while (!s.empty()) {cout << s.top() << " "; s.pop();}return 0;
}
  1. 優先使用對象組合,而不是類繼承 。
  2. 繼承允許你根據基類的實現來定義派生類的實現。這種通過生成派生類的復用通常被稱為白箱復用(white-box reuse)。術語“白箱”是相對可視性而言:在繼承方式中,基類的內部細節對子類可見 。繼承一定程度破壞了基類的封裝,基類的改變,對派生類有很大的影響。派生類和基類間的依賴關系很強,耦合度高。
  3. 對象組合是類繼承之外的另一種復用選擇。新的更復雜的功能可以通過組裝或組合對象來獲得。對象組合要求被組合的對象具有良好定義的接口。這種復用風格被稱為黑箱復用(black-box reuse),因為對象的內部細節是不可見的。對象只以“黑箱”的形式出現。組合類之間沒有很強的依賴關系,耦合度低。優先使用對象組合有助于你保持每個類被封裝
  4. 實際盡量多去用組合。組合的耦合度低,代碼維護性好。不過繼承也有用武之地的,有些關系就適合繼承那就用繼承,另外要實現多態,也必須要繼承。類之間的關系可以用繼承,可以用組合,就用組合。

總結

到這里繼承的相關知識小編就基本分享完了咯,如果有什么疑問歡迎大家討論。那今天就到這里吧。
在這里插入圖片描述

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

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

相關文章

洛谷 B3647:【模板】Floyd 算法

【題目來源】 https://www.luogu.com.cn/problem/B3647 【題目描述】 給出一張由 n 個點 m 條邊組成的無向圖。 求出所有點對 (i,j) 之間的最短路徑。 【輸入格式】 第一行為兩個整數 n&#xff0c;m&#xff0c;分別代表點的個數和邊的條數。 接下來 m 行&#xff0c;每行三…

netlist

在電子設計自動化&#xff08;EDA&#xff09;中&#xff0c;網表&#xff08;Netlist&#xff09; 是描述電路設計連接關系的核心數據結構&#xff0c;本質上是電路元件&#xff08;如邏輯門、晶體管、模塊&#xff09;及其互連關系的 文本化或結構化表示。它是從抽象設計&…

Cadence學習筆記之---原理圖設計基本操作

目錄 01 | 引 言 02 | 環境描述 03 | 原理圖工具介紹 04 | 原理圖設計基本操作 05 | 生成頁間引用 06 | 元件自動編號 07 | 結 尾 01 | 引 言 書接上回&#xff0c;在前文中講述了怎樣制作常用的庫元件&#xff0c;如電阻、二極管&#xff0c;IC器件&#xff0c;以及怎…

【華為HCIP | 華為數通工程師】821—多選解析—第十七頁

多選835、IS-IS協議所使用的NSAP地址主要由哪幾個部分構成? A、AREA ID B、SEL C、DSCp D、SYSTEM ID 解析:NSAP地址:網絡服務訪問點(Network Service Access Point)是 OSI 協議中用于定位資源的地址。NSAP 的地址結構如圖所示,它由 IDP(Initial Domain …

Linux系統中命令設定臨時IP

1.查看ip ---ifconfig 進入指定的網絡接口 ifconfig ens160 建立服務器臨時IP ifconfig ens160 ip地址 network 系統進行重啟后&#xff0c;臨時IP將會消失 ip address add ip地址 dev 服務器 ---添加臨時ip ip address delete ip地址 dev 服務器 ---刪除臨時ip 設置ip&a…

深度學習之卷積神經網絡入門

一、引言 在深度學習蓬勃發展的今天&#xff0c;卷積神經網絡&#xff08;Convolutional Neural Network&#xff0c;簡稱 CNN&#xff09;憑借其在圖像識別、計算機視覺等領域的卓越表現&#xff0c;成為了人工智能領域的核心技術之一。從手寫數字識別到復雜的醫學影像分析&a…

使用RabbitMQ實現判題功能

這次主要選用RabbitMQ消息隊列來對判題服務和題目服務解耦&#xff0c;題目服務只需要向消息隊列發送消息&#xff0c;判題服務從消息隊列中取信息去執行判題&#xff0c;然后異步更新數據庫即可。 五一寶寶請快點跑~~~~~ 先回顧一下RabbitMQ &#xff08;1&#xff09;引入依…

HTML5后臺管理界面開發

HTML5后臺管理界面開發 隨著互聯網技術的快速發展&#xff0c;后臺管理系統在各個業務領域中扮演著越來越重要的角色。它不僅幫助企業管理數據、用戶和業務流程&#xff0c;也為決策提供了依據。本文將介紹如何使用HTML5開發一個簡單的后臺管理界面&#xff0c;并結合代碼示例…

Oracle 11g RAC手動打補丁詳細步驟

備份&#xff1a; 節點1&#xff1a; root用戶備份GI_home tar cvf Ghome_backup.tar /oracle/grid/crsoracle用戶備份ORACLE_HOME tar cvf ohome_backup.tar $ORACLE_HOME節點2&#xff1a; root用戶備份GI_home tar cvf Ghome_backup.tar /oracle/grid/crsoracle用戶備份…

xfce桌面漢化設置

文章目錄 漢化配置小結 漢化配置 檢查當前語言環境&#xff0c;執行指令locale&#xff0c;如果輸出的 LANG、LC_ALL 等未包含 zh_CN.UTF-8&#xff0c;需要設置中文環境。 安裝中文語言包 sudo apt update sudo apt install language-pack-zh-hans language-pack-zh-hant設置…

如何在IDEA中高效使用Test注解進行單元測試?

在軟件開發過程中&#xff0c;單元測試是保證代碼質量的重要手段之一。而IntelliJ IDEA作為一款強大的Java開發工具&#xff0c;提供了豐富的功能來支持JUnit測試&#xff0c;尤其是通過Test注解可以快速編寫和運行單元測試。那么&#xff0c;如何在IDEA中高效使用Test注解進行…

Linux 路由

Linux路由表 一&#xff1a;查看路由二&#xff1a;添加路由三&#xff1a;刪除路由四&#xff1a;路由測試五&#xff1a;路由選擇機制1.路由表2.路由匹配機制3.策略路由 示例1.多網卡分流2.VPN分流3.雙默認路由負載均衡 一&#xff1a;查看路由 # 查看 main 表 ip route sho…

x-cmd install | brows - 終端里的 GitHub Releases 瀏覽器,告別繁瑣下載!

目錄 核心功能與優勢安裝適用場景 還在為尋找 GitHub 項目的特定 Release 版本而苦惱嗎&#xff1f;還在網頁上翻來覆去地查找下載鏈接嗎&#xff1f;現在&#xff0c;有了 brows&#xff0c;一切都將變得簡單高效&#xff01; brows 是一款專為終端設計的 GitHub Releases 瀏覽…

Vue多地址代理端口調用

第一種方法 config.ts文件 配置多條代理服務端口 如下所示:proxy: {/app: {// 其他的端口target: http://125.124.5.117:12877/,changeOrigin: true}/api: {//默認的端口// http://192.168.31.53:5173/target: http://192.168.31.199:18777/,changeOrigin: true,rewrite: pat…

青少年編程與數學 02-018 C++數據結構與算法 10課題、搜索[查找]

青少年編程與數學 02-018 C數據結構與算法 10課題、搜索[查找] 一、線性搜索&#xff08;Linear Search&#xff09;原理實現步驟代碼示例&#xff08;C&#xff09;復雜度分析優缺點 二、二分搜索&#xff08;Binary Search&#xff09;原理代碼示例&#xff08;C&#xff09;…

Linux操作系統從入門到實戰(三)Linux基礎指令(上)

Linux操作系統從入門到實戰&#xff08;三&#xff09;Linux基礎指令&#xff08;上&#xff09; 前言一、ls 指令二、pwd三、cd四、touch 指令五、mkdir六、rmdir 指令和 rm 指令七、man 指令八、cp九、mv 指令十、cat 指令十一、 more 指令十二、less 指令十四、head 指令十五…

Java對象轉換的多種實現方式

Java對象轉換的多種實現方式 在Java開發中&#xff0c;對象轉換是一個常見的需求。特別是在不同層次間傳遞數據時&#xff0c;通常需要將一個對象轉換為另一個對象。雖然JSON序列化/反序列化是一種常見的方法&#xff0c;但在某些場景下可能并不是最佳選擇。本文將總結幾種常見…

頭歌實訓之索引

&#x1f31f; 各位看官好&#xff0c;我是maomi_9526&#xff01; &#x1f30d; 種一棵樹最好是十年前&#xff0c;其次是現在&#xff01; &#x1f680; 今天來學習C語言的相關知識。 &#x1f44d; 如果覺得這篇文章有幫助&#xff0c;歡迎您一鍵三連&#xff0c;分享給更…

Rundeck 介紹及安裝:自動化調度與執行工具

Rundeck介紹 概述&#xff1a;Rundeck 是什么&#xff1f; Rundeck 是一款開源的自動化調度和任務執行工具&#xff0c;專為運維場景設計&#xff0c;幫助工程師通過統一的平臺管理和執行跨系統、跨節點的任務。它由 PagerDuty 維護&#xff08;2016 年收購&#xff09;&#…

基于 Python 的自然語言處理系列(85):PPO 原理與實踐

&#x1f4cc; 本文介紹如何在 RLHF&#xff08;Reinforcement Learning with Human Feedback&#xff09;中使用 PPO&#xff08;Proximal Policy Optimization&#xff09;算法對語言模型進行強化學習微調。 &#x1f517; 官方文檔&#xff1a;trl PPOTrainer 一、引言&…