淺談C++不同繼承之間的關系
公有繼承:“是一個” 的關系
派生類與基類:
賦值兼容規(guī)則
C++面向?qū)ο缶幊讨幸粭l重要的規(guī)則是:公有繼承意味著 “是一個” 。一定要牢牢記住這條規(guī)則。在任何需要基類對象的地方都可以用公有派生類的對象來代替,這條規(guī)則稱賦值兼容規(guī)則。它包括
以下情況:
- 派生類的對象可以賦值給基類的對象,這時是把派生類對象中從對應基類中繼承來的隱藏對象賦值給基類對象。反過來不行,因為派生類的新成員無值可賦。
- 可以將一個派生類的對象的地址賦給其基類的指針變量,但只能通過這個指針訪問派生類中由基類繼承來的隱藏對象,不能訪問派生類中的新成員。同樣也不能反過來做。
- 派生類對象可以初始化基類的引用。引用是別名,但這個別名只能包含派生類對象中的由基類繼承來的隱藏對象。
如下代碼示例:
class Object { public: int value; public: Object(int x = 0) :value(x) {} ~Object() {} void print(int x) { value = x; cout << value << endl; } }; class Base : public Object { public: int num; public: Base(int x = 0):Object(x),num(x+10) {} }; int main() { Base base(10); Object obja(0); Object *op = &base; Object &ob = base; obja = base; return 0; }
繼承關系中的構(gòu)造函數(shù)與析構(gòu)函數(shù)
class Person { int _id; public: Person(int id) :_id(id) { cout << "Create Person " << this << endl; } ~Person() { cout << "Destroy Person " << this << endl; } }; class Student : public Person { int _s_id; public: Student(int id, int s, int n) :_s_id(s), Person(id) { cout << "Create Student: " << this << endl; } ~Student() { cout << "Destroy Student" << this << endl; } }; int main() { Student stud(90010, 202201, 23); return 0; }
定義基類person,派生類student,當在主函數(shù)中創(chuàng)建一個派生類對象時,首先創(chuàng)建person對象,再創(chuàng)建student對象,析構(gòu)時,先析構(gòu)派生類對象,再析構(gòu)基類對象
繼承關系中拷貝構(gòu)造函數(shù)
- 程序設計者在基類和派生類中都沒有定義拷貝構(gòu)造函數(shù);C++編譯器將自動產(chǎn)生按位拷貝的拷貝構(gòu)造函數(shù);在派生類的拷貝構(gòu)造函數(shù)的初始化表中,加入基類拷貝構(gòu)造函數(shù)的調(diào)用,是C++編譯器合成的代碼;
- 程序設計者在基類中定義拷貝構(gòu)造函數(shù);而在派生類中沒有定義拷貝構(gòu)造函數(shù);C++編譯器將會在派生類中自動產(chǎn)生按位拷貝的拷貝構(gòu)造函數(shù)。并合成代碼,調(diào)用(關聯(lián))基類的拷貝構(gòu)造函數(shù)。
- 程序設計者在基類和派生類中都定義了拷貝構(gòu)造函數(shù);程序設計者在派生類中,沒有指定調(diào)用基類的拷貝構(gòu)造函數(shù)時。C++編譯器合成的代碼調(diào)用基類的缺省構(gòu)造函數(shù),如果基類中沒有缺省構(gòu)造函數(shù)。合成代碼失敗。
- 程序設計者在基類中沒有定義拷貝構(gòu)造函數(shù)(C++編譯器將自動產(chǎn)生按位拷貝的拷貝構(gòu)造函數(shù))。而在派生類中定義了拷貝構(gòu)造函數(shù)。程序設計者在派生類中,沒有指定調(diào)用基類的拷貝構(gòu)造函數(shù)時。C++編譯器合成的代碼調(diào)用基類的缺省構(gòu)造函數(shù),如果基類中沒有缺省構(gòu)造函數(shù)。
繼承關系中賦值運算符的重載
- 程序設計者在基類和派生類中都沒有重載operator=函數(shù); C++編譯器將在基類和派生類中自動產(chǎn)生按位賦值的,重載operator=函數(shù);C++編譯器會在派生類的重載賦值函數(shù)中,加入基類重載賦值函數(shù)的調(diào)用,是C++編譯器合成的代碼;(完成行為的統(tǒng)一);
- 程序設計者在基類中定義重載賦值函數(shù);而在派生類中沒有定義重載賦值函數(shù);C++編譯器將會在派生類中自動產(chǎn)生按位賦值的重載賦值函數(shù)。并合成代碼,調(diào)用(關聯(lián))基類的重載賦值函數(shù)。
- 程序設計者在基類和派生類中都定義了重載賦值函數(shù);程序設計者在派生類中,沒有指定調(diào)用基類的重載賦值函數(shù)時。C++編譯器不會合成調(diào)用基類的重載賦值函數(shù)的代碼。要在派生類的重載賦值函數(shù)調(diào)用基類的重載賦值函數(shù),程序設計者必須自己加入調(diào)用代碼。
- 程序設計者在基類中沒有定義重載賦值函數(shù)(C++編譯器將自動產(chǎn)生按位賦值的重載賦值函數(shù)。而在派生類中定義了重載賦值函數(shù)。程序設計者在派生類中,沒有指定調(diào)用基類的重載賦值函數(shù)。C++編譯器不會合成調(diào)用基類的重載賦值函數(shù)的代碼。
通過組合體現(xiàn) “有一個” 或 “用…來實現(xiàn)”
使某個類的對象成為另一個類的數(shù)據(jù)成員,從而實現(xiàn)將一個類構(gòu)筑在另一個類之上,這一過程稱為
"組合“,分層;
組合
通過組合來體現(xiàn) “有一個” 或 “用…來實現(xiàn)”。
例如,“汽車有一個發(fā)動機 或 汽車用發(fā)動機來實現(xiàn) ” (has-a) 關系可以用單一組合表示為:
class Engine // 發(fā)動機 { private: int cylinderNum; // 氣缸數(shù) public: Engine(int n = 4) :cylinderNum(n) {} void Start(); // 啟動 }; class Car { private: Engine eg; public: Car():eg(8) {} void StartCar(); };
**組合關系:**通過組合體現(xiàn) “有一個” 或 “用…來實現(xiàn)”。組合是一種耦合度更強的關聯(lián)關系。存在組合關系的類表示“整體-部分”的關聯(lián)關系,“整體”負責“部分”的生命周期,他們之間是共生共死的;并且“部分”單獨存在時沒有任何意義。
同樣的“有一個”關系也能用私有繼承表示:
class Engine // 發(fā)動機 { private: int cylinderNum; // 氣缸數(shù) public: Engine(int n = 4) :cylinderNum(n) {} void EnStart(); // 啟動 }; class Door { private: int doorNum; public: Door(int n = 5) :doorNum(n) {} } class Car : private Engine { public: Car() :Engine(8) {} void StartCar(); //通過發(fā)動引擎來發(fā)動這輛汽車 };
私有繼承: 要表示類之間 “用…來實現(xiàn)” 的關系,可以選擇是通過私有繼承實現(xiàn)?,F(xiàn)在這種情況下,這一技術(shù)就比分層更有優(yōu)勢,因為通過它可以讓你告訴別人:Engine使用起來不安全,它只能用來實現(xiàn)其它的類
**聚合關系:**通過聚合體現(xiàn) “有一個” 或 “用…來實現(xiàn)”。 整體類與局部類之間松耦合,相互獨立。
class Engine // 發(fā)動機 { private: int cylinderNum; // 氣缸數(shù) public: Engine(int n = 4) :cylinderNum(n) {} void Start(); // 啟動 }; class Car { private: Engine *peg; public: Car():peg(nullptr) {} void SetEngine(Engine *p) { peg = p;} void StartCar(); };
總結(jié)
公有繼承與組合的區(qū)別
繼承與組合都是面向?qū)ο笾写a復用的方式。
公有繼承: 父類的內(nèi)部細節(jié)對子類可見,其代碼屬于白盒式的復用;
例如: class Person ; class Student;
公有繼承的優(yōu)缺點
優(yōu)點:
- 支持擴展,通過繼承父類,可以設計較為復雜的系統(tǒng),體現(xiàn)了由簡單到復雜的認識過程。
- 易于修改被復用的代碼。
缺點:
- 代碼白盒復用,父類的實現(xiàn)細節(jié)暴露給子類,破壞了封裝性。
- 當父類的實現(xiàn)代碼修改時,可能使得子類也不得不修改,增加維護難度。
- 子類缺乏獨立性,依賴于父類,耦合度較高。
- 不支持動態(tài)拓展,在編譯期就決定了父類。
組合和私有繼承
**組合:**意味著 “用…來實現(xiàn)”; 對象之間的內(nèi)部細節(jié)不可見,其代碼屬于黑盒式復用。
私有繼承意味著 “用…來實現(xiàn)”; 是組合關系,父類的內(nèi)部細節(jié)對子類不可見,其代碼屬于黑盒式復用。
優(yōu)點:
- 代碼黑盒復用,被包括的對象內(nèi)部實現(xiàn)細節(jié)對外不可見,封裝性好。
- 整體類與局部類之間松耦合,相互獨立。
- 支持擴展每個類只專注于一項任務
- 支持動態(tài)擴展,可在運行時根據(jù)具體對象選擇不同類型的組合對象(擴展性比繼承好)。
缺點:
- 創(chuàng)建整體類對象時,需要創(chuàng)建所有局部類對象。導致系統(tǒng)對象很多。
公有繼承與私有繼承和組合如何選擇?
在對象分析時明確具有是一個(is - a) 的關系,使用公有繼承。
在對象分析時明確具有 “有一個” 或 "用…來實現(xiàn)"關系,使用組合和私有繼承。
私有繼承和組合如何選擇?
答案很簡單:盡可能地使用組合,必須時才使用私有繼承。什么時候必須呢?這往往是指有保護成員
和/或虛函數(shù)介入的時候考慮考慮使用私有繼承。
//私有繼承在編碼過程中就要指定具體的父類,其關系在編譯期就確定,而組合的關系一般在運行時確定。
到此這篇關于淺談C++不同繼承之間的關系的文章就介紹到這了,更多相關C++繼承關系內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C++中4種管理數(shù)據(jù)內(nèi)存的方式總結(jié)
根據(jù)用于分配內(nèi)存的方法,C++中有3中管理數(shù)據(jù)內(nèi)存的方式:自動存儲、靜態(tài)存儲和動態(tài)存儲。在存在時間的長短方面,以這三種方式分配的數(shù)據(jù)對象各不相同。下面簡要介紹這三種類型2022-09-09C語言實現(xiàn) 數(shù)據(jù)類型占多少字節(jié)指針占多少字節(jié)
這篇文章主要介紹了 C語言 數(shù)據(jù)類型占多少字節(jié)指針占多少字節(jié)的實例代碼,代碼簡單易懂,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2019-09-09探討:用兩個棧實現(xiàn)一個隊列(我作為面試官的小結(jié))
作為面試官的我,經(jīng)常拿這道用兩個棧實現(xiàn)一個隊列的面試題來考面試者,通過對面試者的表現(xiàn)和反應,有一些統(tǒng)計和感受,在此做個小結(jié)2013-05-05