C++从0到1的入门级教学(十三)——继承
文章目錄
- 13 繼承
- 13.1 繼承的基本語(yǔ)法
- 13.2 繼承方式
- 13.3 繼承的對(duì)象模型
- 13.4 繼承中構(gòu)造和析構(gòu)順序
- 13.5 繼承同名成員處理方式
- 13.6 繼承同名靜態(tài)成員處理方式
- 13.7 多繼承語(yǔ)法
- 13.8 菱形繼承
13 繼承
繼承是面向?qū)ο笕筇匦灾弧S行╊惡皖愔g存在特殊的關(guān)系,如下圖所示:
我們發(fā)現(xiàn),定義這些類的時(shí)候,每個(gè)子節(jié)點(diǎn)都有父節(jié)點(diǎn)的特性,且有自己的特性。如果在定義類的時(shí)候,有諸如以上的關(guān)系,那么我們?cè)贑++中和Java中都可以采用繼承的方式來簡(jiǎn)化代碼。
13.1 繼承的基本語(yǔ)法
繼承的語(yǔ)法是class 子類類名:繼承方式 父類類名。讓我們看一下下面的例子:
#include <iostream> using namespace std;//class Java //{ //public: // void header() // { // cout << "首頁(yè)、公開課、登錄、注冊(cè)...公共頭部" << endl; // } // void footer() // { // cout << "幫助中心、交流合作、站內(nèi)地圖...(公共底部)" << endl; // } // void left() // { // cout << "Java、Python、C++、...(公共分類列表)" << endl; // } // void content() // { // cout << "Java學(xué)科視頻" << endl; // } //}; // //class Python //{ //public: // void header() // { // cout << "首頁(yè)、公開課、登錄、注冊(cè)...公共頭部" << endl; // } // void footer() // { // cout << "幫助中心、交流合作、站內(nèi)地圖...(公共底部)" << endl; // } // void left() // { // cout << "Java、Python、C++、...(公共分類列表)" << endl; // } // void content() // { // cout << "Python學(xué)科視頻" << endl; // } //}; // //class CPP //{ //public: // void header() // { // cout << "首頁(yè)、公開課、登錄、注冊(cè)...公共頭部" << endl; // } // void footer() // { // cout << "幫助中心、交流合作、站內(nèi)地圖...(公共底部)" << endl; // } // void left() // { // cout << "Java、Python、C++、...(公共分類列表)" << endl; // } // void content() // { // cout << "C++學(xué)科視頻" << endl; // } //};//繼承公共頁(yè)面 class BasePage { public:void header(){cout << "首頁(yè)、公開課、登錄、注冊(cè)...公共頭部" << endl;}void footer(){cout << "幫助中心、交流合作、站內(nèi)地圖...(公共底部)" << endl;}void left(){cout << "Java、Python、C++、...(公共分類列表)" << endl;} };//Java頁(yè)面 class Java :public BasePage { public :void content() {cout << "Java學(xué)科視頻" << endl;} };//Java頁(yè)面 class Python :public BasePage { public:void content(){cout << "Python學(xué)科視頻" << endl;} };//C++頁(yè)面 class CPP :public BasePage { public:void content(){cout << "C++學(xué)科視頻" << endl;} };void test01() {cout << "Java 下載視頻頁(yè)面如下:" << endl;Java java;java.header();java.footer();java.left();java.content();cout << "Python 下載視頻頁(yè)面如下:" << endl;Python python;python.header();python.footer();python.left();python.content();cout << "C++ 下載視頻頁(yè)面如下:" << endl;CPP cpp;cpp.header();cpp.footer();cpp.left();cpp.content(); }int main() {test01(); }子類有時(shí)候也叫派生類,而父類有時(shí)候也叫基類。
繼承的使用如果是對(duì)于學(xué)習(xí)過Java的人來說是非常簡(jiǎn)單的,但不同于Java的是,其并不是像Java一樣是單繼承而是多繼承,這個(gè)觀點(diǎn)我們?cè)诤竺娉掷m(xù)提到,不用著急。
13.2 繼承方式
我們已經(jīng)知道繼承的語(yǔ)法怎么使用了,但是我們對(duì)繼承的方式還是了解頗淺,在這一小節(jié)中,讓我們看下繼承有哪些方式吧。
繼承的方式一共有三類:
- 公共繼承
- 保護(hù)繼承
- 私有繼承
讓我們看一下繼承方式的示意圖:
對(duì)于父類私有的東西,子類無論如何繼承都無法得到,這是其一;除了私有權(quán)限的東西,其他的都好說,如果是公共繼承,那么子類會(huì)將父類的公有屬性和保護(hù)屬性繼承,并且不變其權(quán)限;如果是保護(hù)繼承,那么在繼承父類除私有屬性外,其他屬性一律變?yōu)樽宇惐Wo(hù)屬性;同樣地,如果是私有繼承,那么在繼承父類除私有屬性外,其他屬性一律變?yōu)樽宇愃接袑傩浴?/p> #include <iostream> using namespace std;//繼承方式 //公共繼承 class Base1 { public:int m_A; protected:int m_B; private:int m_C; };class Son1 :public Base1 { public:void func() {m_A = 10;//父類中的公共權(quán)限成員,到子類中依然是公共權(quán)限m_B = 10;//父類中的保護(hù)權(quán)限成員,到了子類中依然是保護(hù)權(quán)限//m_C = 10;不可訪問,父類中的私有權(quán)限成員,子類繼承不了} };void test01() {Son1 s1;s1.m_A;//公共權(quán)限類內(nèi)可訪問,類外也可訪問//s1.m_B;保護(hù)權(quán)限類內(nèi)可訪問,類外不可訪問 }//保護(hù)繼承 class Base2 { public:int m_A; protected:int m_B; private:int m_C; };class son2 :protected Base2 { public:void func() {m_A = 100;//父類中公共成員,到了子類中變成保護(hù)權(quán)限m_B = 100;//父類中保護(hù)成員,到了子類中變成了保護(hù)權(quán)限//m_C = 100;即使是保護(hù)繼承,子類依然得不到父類私有成員} };void test02() {son2 s2;//s2.m_A = 100;//在son2中m_A變?yōu)楸Wo(hù)權(quán)限,因此類外訪問不到//s2.m_B = 100;//與上面同理,不多解釋 }//私有繼承 class Base3 { public:int m_A; protected:int m_B; private:int m_C; };class son3 : private Base3 { public:void func(){m_A = 100;//父類中公共成員,到了子類中變成私有權(quán)限m_B = 100;//父類中保護(hù)成員,到了子類中變成了私有權(quán)限//m_C = 100;即使是保護(hù)繼承,子類依然得不到父類私有成員} };void test03() {son3 s3;//s3.m_A = 100;//在son3中m_A變?yōu)樗接袡?quán)限,因此類外訪問不到//s3.m_B = 100;//與上面同理,不多解釋 }int main() {test01();test02();test03(); }
13.3 繼承的對(duì)象模型
有時(shí)候我們應(yīng)該提出一個(gè)疑問,對(duì)于從父類繼承而來的成員,哪些屬于子類對(duì)象?
從下面的代碼中,我們相信可以找到自己想要的答案。
#include <iostream> using namespace std;//繼承中的對(duì)象模型 class Base { public:int m_A; protected:int m_B; private:int m_C; };class Son : public Base { public:int m_D; };void test01() {cout << "size of Son = " << sizeof(Son) << endl; }int main() {test01(); }out:
為什么會(huì)這樣?
從結(jié)果來看,父類中非靜態(tài)成員屬性都會(huì)被子類繼承下去。即使是私有的成員,雖然編譯器不能讓其訪問,但是還是能夠繼承下來。
我們想要驗(yàn)證這個(gè)猜想。
對(duì)此我們可以打開Vistul stdio的開發(fā)者命令行工具。
打開完后。我們回來找到我們的代碼文件對(duì)應(yīng)的位置。
復(fù)制備用。
我們使用linux命令在開發(fā)者命令行工具中實(shí)現(xiàn)文件路徑跳轉(zhuǎn)。
cd C:\ cd C:\UserWorkStation\3_代碼\C++\Test01然后使用以下命令查看該文件夾下的文件。
dir用以下的命令——報(bào)告單個(gè)類分布圖+類名字+文件名字。
cl /d1 reportSingleClassLayoutSon Test01.cpp從圖上我們可以看到,實(shí)際上父類的私有成員也被子類Son繼承下來了,之所以不能用只不過是受編譯器的影響無法訪問罷了 。
13.4 繼承中構(gòu)造和析構(gòu)順序
子類繼承父類后,當(dāng)創(chuàng)建子類對(duì)象,也會(huì)調(diào)用父類的構(gòu)造函數(shù)。那么此時(shí)我們引出問題:父類和子類的構(gòu)造和析構(gòu)順序是誰(shuí)先誰(shuí)后呢?從下面的代碼中,我們或許能夠找到我們的答案。
#include <iostream> using namespace std;class Base { public:Base() {cout << "Base構(gòu)造函數(shù)!" << endl;}~Base() {cout << "Base析構(gòu)函數(shù)!" << endl;} };class Son :public Base { public:Son() {cout << "Son構(gòu)造函數(shù)!" << endl;}~Son() {cout << "Son析構(gòu)函數(shù)!" << endl;} };void test01() {Son s; }int main() {test01(); }out:
其實(shí)這個(gè)結(jié)果是顯而易見的。因?yàn)槟銢]有父類哪來的子類,而且子類的供給來源于父類,要想銷毀父類必須先把子類給消除。
13.5 繼承同名成員處理方式
我們不禁拋出一個(gè)問題,當(dāng)子類和父類出現(xiàn)同名的成員時(shí),如何通過子類對(duì)象,訪問到子類或父類中同名的數(shù)據(jù)呢?
很簡(jiǎn)單,加修飾符即可。對(duì)于訪問子類同名的成員,直接訪問即可,但是如果訪問父類同名成員,加上作用域。
跟著下面的代碼敲一下,我相信你能夠理解我說的是什么。
#include <iostream> using namespace std;class Base { public :Base() {m_A = 100;}int m_A;void func() {cout << "Base-func()調(diào)用" << endl;} };class Son :public Base { public:Son() {m_A = 200;}int m_A;void func(){cout << "Son-func()調(diào)用" << endl;} };//同名成員屬性的處理方式 void test01() {Son s;cout << "Son的m_A = " << s.m_A << endl;//如果通過子類對(duì)象 訪問到父類中同名成員,需要加上作用域cout << "Base的m_A = " << s.Base::m_A << endl; }//同名成員函數(shù)的處理方式 void test02() {Son s;s.func();s.Base::func(); }int main() {//test01();test02(); }在上面的代碼中,明顯地,如果想要調(diào)用子類的同名函數(shù),直接按平時(shí)那么調(diào)用就行了;但是如果想要調(diào)用父類的同名函數(shù),那么就需要加上父類::成員實(shí)現(xiàn)調(diào)用。
還有一個(gè)需要考慮的問題是,我們前面講過函數(shù)重載。函數(shù)重載時(shí),相同名字的函數(shù)就會(huì)變多,此時(shí)又會(huì)發(fā)生什么情況呢?
我們?cè)囍鴮?duì)父類的同名函數(shù)重載。
class Base { public :Base() {m_A = 100;}int m_A;void func() {cout << "Base-func()調(diào)用" << endl;}void func(int a) {a = 100;} };這時(shí)候如果想要試著不加作用域在子類對(duì)象中調(diào)用父類的重載函數(shù),那么會(huì)失敗。
因?yàn)樵谀悴患幼饔糜驎r(shí),系統(tǒng)會(huì)默認(rèn)為你想要調(diào)用的是子類對(duì)象中的同名函數(shù),而同名函數(shù)沒有重載,重載的是父類的;換而言之,當(dāng)子類的父類出現(xiàn)同名函數(shù)時(shí),如果通過子類對(duì)象調(diào)用,那么父類的所有同名函數(shù)都會(huì)被屏蔽,而如果想要得到被屏蔽的函數(shù),加上作用域就能解決這一問題。
13.6 繼承同名靜態(tài)成員處理方式
在前面的敘述中,我們談?wù)摰亩际欠庆o態(tài)成員。這這一小節(jié)中,我們?cè)囍務(wù)撘幌蚂o態(tài)成員。
我們不禁發(fā)問,靜態(tài)成員和非靜態(tài)成員處理方法一樣嗎?答案是一樣的。
我們?cè)囍靡幌孪旅娴拇a,它能告訴我們答案。
#include <iostream> using namespace std;//繼承中的同名靜態(tài)成員處理方式 class Base { public:static int m_A;static void func() {cout << "Base_func" <<endl;} }; int Base::m_A = 100;class Son:public Base { public:static int m_A;static void func(){cout << "Son_func" << endl;} }; int Son::m_A = 200;//同名靜態(tài)成員屬性 void test01() {//1、通過對(duì)象訪問Son s;cout << "Son的m_A = " << s.m_A << endl;cout << "Base的m_A = " << s.Base::m_A << endl;//2、通過類名訪問cout << "Son的m_A = " << Son::m_A << endl;cout << "Base的m_A = " << Son::Base::m_A << endl; }//同名靜態(tài)成員函數(shù) void test02() {//1、通過對(duì)象訪問Son s;s.func();s.Base::func();//2、通過類名訪問Son::func();Son::Base::func(); }int main() {//test01();test02(); }13.7 多繼承語(yǔ)法
不同于Java,C++有著多繼承模式。換而言之,C++允許一個(gè)子類繼承多個(gè)父類。其語(yǔ)法如下:
class 子類:繼承方式 父類1,繼承方式 父類2…
當(dāng)然,多繼承會(huì)伴隨著同名成員的出現(xiàn),所以需要加上作用域加以區(qū)分。
在實(shí)際開發(fā)中,是不推薦使用多繼承的。
讓我們?cè)囍靡幌孪旅娴拇a,仔細(xì)體會(huì)其中的意境。
#include <iostream> using namespace std;//多繼承語(yǔ)法 class Base1 { public:Base1() {m_A = 100;}int m_A; };class Base2 { public:Base2(){m_B = 200;}int m_B; };class Son :public Base1, public Base2 { public:Son() {m_C = 300;m_D = 400;}int m_C;int m_D; };void test01() {Son s;cout << "sizeof Son = " << sizeof(s) << endl; }int main() {test01(); }out:
多繼承滿足單繼承的一切特性。也就是說,單繼承出現(xiàn)的問題,多繼承也會(huì)出現(xiàn),這時(shí)候就需要仔細(xì)回顧前面幾個(gè)小節(jié)的知識(shí)了。
13.8 菱形繼承
從名字上看很好理解。菱形繼承就是一個(gè)父類分兩子類,然后又有個(gè)孫子類繼承兩子類。就這么簡(jiǎn)單。當(dāng)你聽到鉆石繼承這個(gè)術(shù)語(yǔ)時(shí),一般也是指菱形繼承。
菱形繼承也會(huì)出現(xiàn)幾個(gè)問題。最簡(jiǎn)單的問題就是,如果兩個(gè)子類有同名成員,那么在孫類的時(shí)候調(diào)用兩個(gè)子類的同名成員就得加上作用域,以防出現(xiàn)二義性。
還有一個(gè)問題是,我們繼承動(dòng)物的數(shù)據(jù)時(shí),由于兩個(gè)子類的繼承,兩個(gè)子類分別占有一個(gè)數(shù)據(jù),當(dāng)孫類繼承兩子類時(shí),就會(huì)繼承兩份動(dòng)物數(shù)據(jù),但是我們很清楚,這份數(shù)據(jù)我們只需要一份,為此,我們需要解決這個(gè)問題。
在下面的代碼中,我們會(huì)提到我們是如何解決這個(gè)問題的。
#include <iostream> using namespace std;//動(dòng)物類 class Animal { public:int m_Age; };//羊類 class Sheep:virtual public Animal {};//駝?lì)?class Tuo :virtual public Animal {};//羊駝?lì)?class YangTuo :public Sheep, public Tuo {};void test01() {YangTuo yt;yt.Sheep::m_Age = 18;yt.Tuo::m_Age = 28;//出現(xiàn)菱形繼承時(shí),兩個(gè)父類擁有相同數(shù)據(jù),需要加上作用域區(qū)分cout << "yt.Sheep::m_Age" << yt.Sheep::m_Age << endl;cout << "yt.Tuo::m_Age" << yt.Tuo::m_Age << endl;//問題2:羊駝的年齡到底是多少?菱形繼承導(dǎo)致了數(shù)據(jù)有兩份,資源浪費(fèi)//解決:利用虛繼承就能夠解決上述問題。cout << "yt.m_Age" << yt.m_Age << endl; }int main() {test01(); }out:
虛繼承、虛基類和虛基類指針
如果我們只想在子類中保留一份數(shù)據(jù),那么我們可以定義虛基類,使派生類中只保留一份拷貝。
虛基類的定義方式是,在子類繼承父類時(shí),在冒號(hào)后繼承方式前間添加virtual關(guān)鍵字,這樣,它們共同繼承的父類我們就叫做虛基類。對(duì)應(yīng)到上述的例子,Animal就是虛基類。
我們使用13.3中學(xué)習(xí)到的開發(fā)者工具看一下類的結(jié)構(gòu)。
從圖中可以看出,虛基類的兩個(gè)子類內(nèi)部供有一個(gè)虛基類表(vbtable),該表中記載著對(duì)于基類數(shù)據(jù)的使用,當(dāng)我們用羊駝?lì)惱^承兩子類時(shí),羊駝?lì)愔袚碛袃筛摶愔羔?#xff0c;分別指向兩個(gè)虛基類表,以便共享同一份數(shù)據(jù)。
總結(jié)
以上是生活随笔為你收集整理的C++从0到1的入门级教学(十三)——继承的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 让你一周变聪明的大脑保健操
- 下一篇: 转~解决VS2017 C++无法打开源文