C++核心程式設計---4.6 類和對象-繼承【P127~P134】
- 4.6 繼承
-
- 4.6.1 繼承的基本文法
- 4.6.2 繼承方式
- 4.6.3 繼承中的對象模型
- 4.6.4 繼承中構造和析構順序
- 4.6.5 繼承同名成員處理方式
- 4.6.6 繼承同名靜态成員處理方式
- 4.6.7 多繼承文法
- 4.6.8 菱形繼承
4.6 繼承
繼承是面向對象三大特性之一
有些類與類之間存在特殊的關系,例如下圖中:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TTq1Eej1mYshmMjVnVuFmb1cVWxgmMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLzUDN3ATNxIjM0ITMwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
會發現,定義這些類時,下級别的成員除了擁有上一級别的共性,還有自己的特性。
這個時候我們就可以考慮利用繼承的技術,減少重複代碼。
4.6.1 繼承的基本文法
例如我們看到很多的網站中,都有公共的頭部公共的底部,甚至公共的左側清單,隻有中心内容不同,接下來我們分别利用普通寫法和繼承的寫法來實作網頁中的内容,看一下繼承存在的意義以及好處。
文法: class 子類 : 繼承方式 父類
子類 也稱為 派生類
父類 也稱為 基類
派生類中的成員,包含兩大部分:
一類是從基類繼承過來的,一類是自己增加的成員。
從基類繼承過來的表現其共性,而新增的成員表現其個性。
普通實作頁面:
#include<iostream>
#include<string>
using namespace std;
//普通實作頁面
//Java 頁面
class Java
{
public:
void header()
{
cout << "首頁、公開課、登入、注冊...(公共頭部)" << endl;
}
void footer()
{
cout << "幫助中心、交流合作、站内地圖...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++...(公共分類清單)" << endl;
}
void content()
{
cout << "Java 學科視訊" << endl;
}
};
//Python 頁面
class Python
{
public:
void header()
{
cout << "首頁、公開課、登入、注冊...(公共頭部)" << endl;
}
void footer()
{
cout << "幫助中心、交流合作、站内地圖...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++...(公共分類清單)" << endl;
}
void content()
{
cout << "Python 學科視訊" << endl;
}
};
//C++ 頁面
class Cpp
{
public:
void header()
{
cout << "首頁、公開課、登入、注冊...(公共頭部)" << endl;
}
void footer()
{
cout << "幫助中心、交流合作、站内地圖...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++...(公共分類清單)" << endl;
}
void content()
{
cout << "C++ 學科視訊" << endl;
}
};
void test01()
{
cout << "Java 下載下傳視訊頁面如下:" << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "--------------------------" << endl;
cout << "Python 下載下傳視訊頁面如下:" << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "--------------------------" << endl;
cout << "Cpp 下載下傳視訊頁面如下:" << endl;
Cpp cp;
cp.header();
cp.footer();
cp.left();
cp.content();
}
int main()
{
test01();
system("pause");
return 0;
}
繼承實作頁面:
#include<iostream>
#include<string>
using namespace std;
//繼承實作頁面
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;
}
};
//Python頁面
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 ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "--------------------------" << endl;
cout << "Python 下載下傳視訊頁面如下:" << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "--------------------------" << endl;
cout << "Cpp 下載下傳視訊頁面如下:" << endl;
Cpp cp;
cp.header();
cp.footer();
cp.left();
cp.content();
}
int main()
{
test01();
system("pause");
return 0;
}
4.6.2 繼承方式
繼承的文法: class 子類 : 繼承方式 父類
繼承方式一共有三種:
- 公共繼承
- 保護繼承
- 私有繼承
C++核心程式設計---4.6 類和對象-繼承【P127~P134】4.6 繼承 第一點:父類中的 private 權限,任何子類均不可以繼承;
第二點:共有繼承—權限不變
第三點:保護繼承—父類中的 public 和 protected 均變化為子類中的 protected;
第四點:私有繼承—父類中的 public 和 protected 均變化為子類中的 private;
#include<iostream>
#include<string>
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 = 100;
//s1.m_B = 100;//報錯!!到Son1中 m_B是保護權限 類外通路不到
}
int main()
{
test01();
system("pause");
return 0;
}
4.6.3 繼承中的對象模型
問題: 從父類繼承過來的成員,哪些屬于子類對象中?
#include<iostream>
#include<string>
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();
//結果是16,父類中所有非靜态成員屬性都會被子類繼承下去,父類中的私有成員屬性是被編譯器隐藏了
//是以是通路不到,但是确實是被繼承下去了
system("pause");
return 0;
}
利用開發人員指令提示工具檢視對象模型
Step1: 打開 VS 2017的開發人員指令提示符 視窗
**********************************************************************
** Visual Studio 2017 Developer Command Prompt v15.9.31
** Copyright (c) 2017 Microsoft Corporation
**********************************************************************
D:\software\Visual Studio 2017>
Step2: cd 到工程檔案路徑下
D:\software\Visual Studio 2017>cd D:\document\vs2017_code\core\004-6 類和對象-繼承\004-6 類和對象-繼承
D:\document\vs2017_code\core\004-6 類和對象-繼承\004-6 類和對象-繼承>
Step3: 檢視對象模型 格式:cl /d1 reportSingleClassLayout類名 "檔案名"
D:\document\vs2017_code\core\004-6 類和對象-繼承\004-6 類和對象-繼承>cl /d1 reportSingleClassLayoutSon "03 繼承中的對象 模型.cpp"
用于 x86 的 Microsoft (R) C/C++ 優化編譯器 19.16.27045 版
版權所有(C) Microsoft Corporation。保留所有權利。
03 繼承中的對象模型.cpp
D:\software\Visual Studio 2017\VC\Tools\MSVC\14.16.27023\include\xlocale(319): warning C4530: 使用了 C++ 異常處理程式, 但未啟用展開語義。請指定 /EHsc
class Son size(16):
+---
0 | +--- (base class Base)
0 | | m_A
4 | | m_B
8 | | m_C
| +---
12 | m_D
+---
Microsoft (R) Incremental Linker Version 14.16.27045.0
Copyright (C) Microsoft Corporation. All rights reserved.
"/out:03 繼承中的對象模型.exe"
"03 繼承中的對象模型.obj"
4.6.4 繼承中構造和析構順序
子類繼承父類後,當建立子類對象,也會調用父類的構造函數
問題:父類和子類的構造和析構順序是誰先誰後??
#include<iostream>
#include<string>
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();
system("pause");
return 0;
}
4.6.5 繼承同名成員處理方式
問題: 當子類與父類出現同名的成員,如何通過子類對象,通路到子類或父類中同名的資料呢?
- 通路子類同名成員 直接通路即可
- 通路父類同名成員 需要加作用域
#include<iostream>
#include<string>
using namespace std;
class Base
{
public:
Base()
{
m_A = 100;
}
void func()
{
cout << "Base - func()調用" << endl;
}
int m_A;
};
class Son : public Base
{
public:
Son()
{
m_A = 200;
}
void func()
{
cout << "Son - func()調用" << endl;
}
int m_A;
};
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()
{
test02();
system("pause");
return 0;
}
總結:
- 子類對象可以直接通路到子類中同名成員
- 子類對象加作用域可以反複問到父類同名成員
- 當子類與父類擁有同名的成員函數,子類會隐藏父類中同名成員函數,加作用域可以通路到父類中同名函數。
4.6.6 繼承同名靜态成員處理方式
問題: 繼承中同名的靜态成員在子類對象上如何進行通路?
靜态成員和非靜态成員出現同名,處理方式一緻
- 通路子類同名成員 直接通路即可
- 通路父類同名成員 需要加作用域
#include<iostream>
#include<string>
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()
{
cout << "通過對象通路:" << endl;
Son s;
cout << "m_A = " << s.m_A << endl;
cout << "m_A = " << s.Base::m_A << endl;
cout << "通過類名通路:" << endl;
cout << "Son 下 m_A = " << Son::m_A << endl;
//第一個::代表通過類名方式通路 第二個::代表通路父類作用域下
cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}
void test02()
{
//通過對象通路
Son s;
s.func();
s.Base::func();
//通過類名通路
Son::func();
Son::Base::func();
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
4.6.7 多繼承文法
C++ 允許一個類繼承多個類
文法:class 子類 : 繼承方式 父類1 , 繼承方式 父類2 …
多繼承問題可能會導緻父類中有同名成員出現,需要加作用域區分
C++ 實際開發過程中不建議使用多繼承
#include<iostream>
#include<string>
using namespace std;
class Base1
{
public:
Base1()
{
m_A = 100;
}
int m_A;
};
class Base2
{
public:
Base2()
{
m_A = 200;
}
int m_A;
};
class Son : public Base1, public Base2
{
public:
Son()
{
m_C = 300;
m_D = 400;
}
int m_C;
int m_D;
};
void test()
{
Son s;
cout << "sizeof Son = " << sizeof(s) << endl;
//當父類中出現同名的成員時,需要加作用域區分
cout << "m_A = " << s.Base1::m_A << endl;
cout << "m_A = " << s.Base2::m_A << endl;
}
int main()
{
test();
system("pause");
return 0;
}
4.6.8 菱形繼承
菱形繼承概念:
- 兩個派生類繼承同一個基類
- 又有某個類同時繼承這兩個派生類
- 這種繼承稱為菱形繼承,或者鑽石繼承
典型繼承案例:
菱形繼承問題:
- 1、羊繼承了動物的資料,駝同樣繼承了動物的資料,當羊駝使用資料時,就會産生二義性;
- 2、羊駝繼承自動物的資料繼承了兩份,其實我們應該清楚,這份資料我們隻要一份就可以。
#include<iostream>
#include<string>
using namespace std;
//動物類
class Animal
{
public:
int m_Age;
};
//利用虛繼承 解決菱形繼承的問題
//繼承之前 加上關鍵字 virtual 變為虛繼承
//Animal 類稱為 虛基類
//羊類
class Sheep : virtual public Animal{};
//駝類
class Tuo : virtual public Animal{};
//羊駝類
class SheepTuo : public Sheep , public Tuo{};
int main()
{
SheepTuo st;
st.Sheep::m_Age = 18;
st.Tuo::m_Age = 28;
//當菱形繼承,兩個父類擁有相同資料,需要加以作用域區分
cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
system("pause");
return 0;
}
D:\software\Visual Studio 2017\VC\Tools\MSVC\14.16.27023\include\xlocale(319): warning C4530: 使用了 C++ 異常處理程式,但未啟用展開語義。請指定 /EHsc
class SheepTuo size(8):
+---
0 | +--- (base class Sheep)
0 | | +--- (base class Animal)
0 | | | m_Age
| | +---
| +---
4 | +--- (base class Tuo)
4 | | +--- (base class Animal)
4 | | | m_Age
| | +---
| +---
+---
Microsoft (R) Incremental Linker Version 14.16.27045.0
Copyright (C) Microsoft Corporation. All rights reserved.
"/out:08 菱形繼承.exe"
"08 菱形繼承.obj"
D:\document\vs2017_code\core\004-6 類和對象-繼承\004-6 類和對象-繼承>cl /d1 reportSingleClassLayoutSheepTuo "08 菱形繼承.cpp"
用于 x86 的 Microsoft (R) C/C++ 優化編譯器 19.16.27045 版
版權所有(C) Microsoft Corporation。保留所有權利。
08 菱形繼承.cpp
D:\software\Visual Studio 2017\VC\Tools\MSVC\14.16.27023\include\xlocale(319): warning C4530: 使用了 C++ 異常處理程式,但未啟用展開語義。請指定 /EHsc
class SheepTuo size(12):
+---
0 | +--- (base class Sheep)
0 | | {vbptr}
| +---
4 | +--- (base class Tuo)
4 | | {vbptr}
| +---
+---
+--- (virtual base Animal)
8 | m_Age
+---
SheepTuo::[email protected]@:
0 | 0
1 | 8 (SheepTuod(Sheep+0)Animal)
SheepTuo::[email protected]@:
0 | 0
1 | 4 (SheepTuod(Tuo+0)Animal)
vbi: class offset o.vbptr o.vbte fVtorDisp
Animal 8 0 4 0
Microsoft (R) Incremental Linker Version 14.16.27045.0
Copyright (C) Microsoft Corporation. All rights reserved.
"/out:08 菱形繼承.exe"
"08 菱形繼承.obj"