C++从0到1的入门级教学(十三)——继承
文章目錄
- 13 繼承
- 13.1 繼承的基本語法
- 13.2 繼承方式
- 13.3 繼承的對象模型
- 13.4 繼承中構造和析構順序
- 13.5 繼承同名成員處理方式
- 13.6 繼承同名靜態成員處理方式
- 13.7 多繼承語法
- 13.8 菱形繼承
13 繼承
繼承是面向對象三大特性之一。有些類和類之間存在特殊的關系,如下圖所示:
我們發現,定義這些類的時候,每個子節點都有父節點的特性,且有自己的特性。如果在定義類的時候,有諸如以上的關系,那么我們在C++中和Java中都可以采用繼承的方式來簡化代碼。
13.1 繼承的基本語法
繼承的語法是class 子類類名:繼承方式 父類類名。讓我們看一下下面的例子:
#include <iostream> using namespace std;//class Java //{ //public: // void header() // { // cout << "首頁、公開課、登錄、注冊...公共頭部" << endl; // } // void footer() // { // cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl; // } // void left() // { // cout << "Java、Python、C++、...(公共分類列表)" << endl; // } // void content() // { // cout << "Java學科視頻" << endl; // } //}; // //class Python //{ //public: // void header() // { // cout << "首頁、公開課、登錄、注冊...公共頭部" << endl; // } // void footer() // { // cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl; // } // void left() // { // cout << "Java、Python、C++、...(公共分類列表)" << endl; // } // void content() // { // cout << "Python學科視頻" << endl; // } //}; // //class CPP //{ //public: // void header() // { // cout << "首頁、公開課、登錄、注冊...公共頭部" << endl; // } // void footer() // { // cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl; // } // void left() // { // cout << "Java、Python、C++、...(公共分類列表)" << endl; // } // void content() // { // cout << "C++學科視頻" << endl; // } //};//繼承公共頁面 class BasePage { public:void header(){cout << "首頁、公開課、登錄、注冊...公共頭部" << endl;}void footer(){cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl;}void left(){cout << "Java、Python、C++、...(公共分類列表)" << endl;} };//Java頁面 class Java :public BasePage { public :void content() {cout << "Java學科視頻" << endl;} };//Java頁面 class Python :public BasePage { public:void content(){cout << "Python學科視頻" << endl;} };//C++頁面 class CPP :public BasePage { public:void content(){cout << "C++學科視頻" << endl;} };void test01() {cout << "Java 下載視頻頁面如下:" << endl;Java java;java.header();java.footer();java.left();java.content();cout << "Python 下載視頻頁面如下:" << endl;Python python;python.header();python.footer();python.left();python.content();cout << "C++ 下載視頻頁面如下:" << endl;CPP cpp;cpp.header();cpp.footer();cpp.left();cpp.content(); }int main() {test01(); }子類有時候也叫派生類,而父類有時候也叫基類。
繼承的使用如果是對于學習過Java的人來說是非常簡單的,但不同于Java的是,其并不是像Java一樣是單繼承而是多繼承,這個觀點我們在后面持續提到,不用著急。
13.2 繼承方式
我們已經知道繼承的語法怎么使用了,但是我們對繼承的方式還是了解頗淺,在這一小節中,讓我們看下繼承有哪些方式吧。
繼承的方式一共有三類:
- 公共繼承
- 保護繼承
- 私有繼承
讓我們看一下繼承方式的示意圖:
對于父類私有的東西,子類無論如何繼承都無法得到,這是其一;除了私有權限的東西,其他的都好說,如果是公共繼承,那么子類會將父類的公有屬性和保護屬性繼承,并且不變其權限;如果是保護繼承,那么在繼承父類除私有屬性外,其他屬性一律變為子類保護屬性;同樣地,如果是私有繼承,那么在繼承父類除私有屬性外,其他屬性一律變為子類私有屬性。
#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;//父類中的公共權限成員,到子類中依然是公共權限m_B = 10;//父類中的保護權限成員,到了子類中依然是保護權限//m_C = 10;不可訪問,父類中的私有權限成員,子類繼承不了} };void test01() {Son1 s1;s1.m_A;//公共權限類內可訪問,類外也可訪問//s1.m_B;保護權限類內可訪問,類外不可訪問 }//保護繼承 class Base2 { public:int m_A; protected:int m_B; private:int m_C; };class son2 :protected Base2 { public:void func() {m_A = 100;//父類中公共成員,到了子類中變成保護權限m_B = 100;//父類中保護成員,到了子類中變成了保護權限//m_C = 100;即使是保護繼承,子類依然得不到父類私有成員} };void test02() {son2 s2;//s2.m_A = 100;//在son2中m_A變為保護權限,因此類外訪問不到//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;//父類中公共成員,到了子類中變成私有權限m_B = 100;//父類中保護成員,到了子類中變成了私有權限//m_C = 100;即使是保護繼承,子類依然得不到父類私有成員} };void test03() {son3 s3;//s3.m_A = 100;//在son3中m_A變為私有權限,因此類外訪問不到//s3.m_B = 100;//與上面同理,不多解釋 }int main() {test01();test02();test03(); }13.3 繼承的對象模型
有時候我們應該提出一個疑問,對于從父類繼承而來的成員,哪些屬于子類對象?
從下面的代碼中,我們相信可以找到自己想要的答案。
#include <iostream> using namespace std;//繼承中的對象模型 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:
為什么會這樣?
從結果來看,父類中非靜態成員屬性都會被子類繼承下去。即使是私有的成員,雖然編譯器不能讓其訪問,但是還是能夠繼承下來。
我們想要驗證這個猜想。
對此我們可以打開Vistul stdio的開發者命令行工具。
打開完后。我們回來找到我們的代碼文件對應的位置。
復制備用。
我們使用linux命令在開發者命令行工具中實現文件路徑跳轉。
cd C:\ cd C:\UserWorkStation\3_代碼\C++\Test01然后使用以下命令查看該文件夾下的文件。
dir用以下的命令——報告單個類分布圖+類名字+文件名字。
cl /d1 reportSingleClassLayoutSon Test01.cpp從圖上我們可以看到,實際上父類的私有成員也被子類Son繼承下來了,之所以不能用只不過是受編譯器的影響無法訪問罷了 。
13.4 繼承中構造和析構順序
子類繼承父類后,當創建子類對象,也會調用父類的構造函數。那么此時我們引出問題:父類和子類的構造和析構順序是誰先誰后呢?從下面的代碼中,我們或許能夠找到我們的答案。
#include <iostream> using namespace std;class Base { public:Base() {cout << "Base構造函數!" << endl;}~Base() {cout << "Base析構函數!" << endl;} };class Son :public Base { public:Son() {cout << "Son構造函數!" << endl;}~Son() {cout << "Son析構函數!" << endl;} };void test01() {Son s; }int main() {test01(); }out:
其實這個結果是顯而易見的。因為你沒有父類哪來的子類,而且子類的供給來源于父類,要想銷毀父類必須先把子類給消除。
13.5 繼承同名成員處理方式
我們不禁拋出一個問題,當子類和父類出現同名的成員時,如何通過子類對象,訪問到子類或父類中同名的數據呢?
很簡單,加修飾符即可。對于訪問子類同名的成員,直接訪問即可,但是如果訪問父類同名成員,加上作用域。
跟著下面的代碼敲一下,我相信你能夠理解我說的是什么。
#include <iostream> using namespace std;class Base { public :Base() {m_A = 100;}int m_A;void func() {cout << "Base-func()調用" << endl;} };class Son :public Base { public:Son() {m_A = 200;}int m_A;void func(){cout << "Son-func()調用" << endl;} };//同名成員屬性的處理方式 void test01() {Son s;cout << "Son的m_A = " << s.m_A << endl;//如果通過子類對象 訪問到父類中同名成員,需要加上作用域cout << "Base的m_A = " << s.Base::m_A << endl; }//同名成員函數的處理方式 void test02() {Son s;s.func();s.Base::func(); }int main() {//test01();test02(); }在上面的代碼中,明顯地,如果想要調用子類的同名函數,直接按平時那么調用就行了;但是如果想要調用父類的同名函數,那么就需要加上父類::成員實現調用。
還有一個需要考慮的問題是,我們前面講過函數重載。函數重載時,相同名字的函數就會變多,此時又會發生什么情況呢?
我們試著對父類的同名函數重載。
class Base { public :Base() {m_A = 100;}int m_A;void func() {cout << "Base-func()調用" << endl;}void func(int a) {a = 100;} };這時候如果想要試著不加作用域在子類對象中調用父類的重載函數,那么會失敗。
因為在你不加作用域時,系統會默認為你想要調用的是子類對象中的同名函數,而同名函數沒有重載,重載的是父類的;換而言之,當子類的父類出現同名函數時,如果通過子類對象調用,那么父類的所有同名函數都會被屏蔽,而如果想要得到被屏蔽的函數,加上作用域就能解決這一問題。
13.6 繼承同名靜態成員處理方式
在前面的敘述中,我們談論的都是非靜態成員。這這一小節中,我們試著談論一下靜態成員。
我們不禁發問,靜態成員和非靜態成員處理方法一樣嗎?答案是一樣的。
我們試著敲一下下面的代碼,它能告訴我們答案。
#include <iostream> using namespace std;//繼承中的同名靜態成員處理方式 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;//同名靜態成員屬性 void test01() {//1、通過對象訪問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; }//同名靜態成員函數 void test02() {//1、通過對象訪問Son s;s.func();s.Base::func();//2、通過類名訪問Son::func();Son::Base::func(); }int main() {//test01();test02(); }13.7 多繼承語法
不同于Java,C++有著多繼承模式。換而言之,C++允許一個子類繼承多個父類。其語法如下:
class 子類:繼承方式 父類1,繼承方式 父類2…
當然,多繼承會伴隨著同名成員的出現,所以需要加上作用域加以區分。
在實際開發中,是不推薦使用多繼承的。
讓我們試著敲一下下面的代碼,仔細體會其中的意境。
#include <iostream> using namespace std;//多繼承語法 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:
多繼承滿足單繼承的一切特性。也就是說,單繼承出現的問題,多繼承也會出現,這時候就需要仔細回顧前面幾個小節的知識了。
13.8 菱形繼承
從名字上看很好理解。菱形繼承就是一個父類分兩子類,然后又有個孫子類繼承兩子類。就這么簡單。當你聽到鉆石繼承這個術語時,一般也是指菱形繼承。
菱形繼承也會出現幾個問題。最簡單的問題就是,如果兩個子類有同名成員,那么在孫類的時候調用兩個子類的同名成員就得加上作用域,以防出現二義性。
還有一個問題是,我們繼承動物的數據時,由于兩個子類的繼承,兩個子類分別占有一個數據,當孫類繼承兩子類時,就會繼承兩份動物數據,但是我們很清楚,這份數據我們只需要一份,為此,我們需要解決這個問題。
在下面的代碼中,我們會提到我們是如何解決這個問題的。
#include <iostream> using namespace std;//動物類 class Animal { public:int m_Age; };//羊類 class Sheep:virtual public Animal {};//駝類 class Tuo :virtual public Animal {};//羊駝類 class YangTuo :public Sheep, public Tuo {};void test01() {YangTuo yt;yt.Sheep::m_Age = 18;yt.Tuo::m_Age = 28;//出現菱形繼承時,兩個父類擁有相同數據,需要加上作用域區分cout << "yt.Sheep::m_Age" << yt.Sheep::m_Age << endl;cout << "yt.Tuo::m_Age" << yt.Tuo::m_Age << endl;//問題2:羊駝的年齡到底是多少?菱形繼承導致了數據有兩份,資源浪費//解決:利用虛繼承就能夠解決上述問題。cout << "yt.m_Age" << yt.m_Age << endl; }int main() {test01(); }out:
虛繼承、虛基類和虛基類指針
如果我們只想在子類中保留一份數據,那么我們可以定義虛基類,使派生類中只保留一份拷貝。
虛基類的定義方式是,在子類繼承父類時,在冒號后繼承方式前間添加virtual關鍵字,這樣,它們共同繼承的父類我們就叫做虛基類。對應到上述的例子,Animal就是虛基類。
我們使用13.3中學習到的開發者工具看一下類的結構。
從圖中可以看出,虛基類的兩個子類內部供有一個虛基類表(vbtable),該表中記載著對于基類數據的使用,當我們用羊駝類繼承兩子類時,羊駝類中擁有兩根虛基類指針,分別指向兩個虛基類表,以便共享同一份數據。
總結
以上是生活随笔為你收集整理的C++从0到1的入门级教学(十三)——继承的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 让你一周变聪明的大脑保健操
- 下一篇: java mybatis cms_jav