C++13(繼承與多型)

2020-08-11 18:26:38

繼承與多型

1.繼承的本質:程式碼複用

1.設計學生類

/*
	設計學生這個類  
		延用人這個類設計好的東西
*/
/*
	人 N多的成員方法
	學生 
		人N多的成員方法
		學生也有人N
*/
#include<iostream>
#include<string>
class People
{
public:
	People(std::string name = " ", bool sex = true, 
		int age = 0)
		:mname(name), msex(sex), mage(age)
	{}
protected:
	std::string mname;
	bool msex;
	int mage;
};
class Student : public People
{
public:
	Student(std::string name = " ", bool sex = true, 
		int age = 0,
		std::string id = " ")
	{
		mid = id;
	}
private:
	std::string mid;
};
//class Student
//{
//public:
//	Student(std::string name, bool sex, int age,
//		std::string id)
//		:mname(name), msex(sex), mage(age),
//		mid(id){}
//private:
//	std::string mname;
//	bool msex;
//	int mage;
//	std::string mid;
//};

2.繼承和派生

在这里插入图片描述

3.派生類的記憶體佈局

在这里插入图片描述

class Base
{
public:
	Base(int a = 0)
		:ma(a)
	{}
	void Show()
	{
		std::cout << "Base::ma : " << ma << std::endl;
	}

	static void Print()
	{
		std::cout << "hello world!" << std::endl;
	}
public:
	int ma;
	static int a;
};
int Base::a = 10;

class Derive :public Base
{
public:
	Derive(int b)
		:mb(b)
	{}
public:
	int mb;
};
int main()
{
	Derive d(20);
	d.mb = 30;
	d.Show();
	Derive::Print();
	std::cout << "Base size : " << sizeof(Base) << std::endl;
	std::cout << "Derive size : " << sizeof(Derive) << std::endl;
	return 0;
}

4.派生類繼承了基礎類別什麼?

如果能繼承構造/解構:基礎類別的建構函式在派生類中,相當於普通函數,但是普通函數必須有返回值,因此不成立,所以派生類不能繼承基礎類別的構造和解構
在这里插入图片描述
1.普通的成員變數
2.靜態的成員變數
3.普通的成員方法
4.靜態的成員方法
5.作用域
派生類繼承了基礎類別除構造解構以外的所有成員

5.派生類的構造解構順序

在这里插入图片描述

/*
	基礎類別沒有給出預設的建構函式
		派生類中指定對應的構造方式和實參 
*/
class Base
{
public:
	Base(int a)
		:ma(a)
	{
		std::cout << "Base::Base()" << std::endl;
	}
	~Base()
	{
		std::cout << "Base::~Base()" << std::endl;
	}
protected:
	int ma;
};
//類標誌 派生類類名 : 繼承方式  基礎類別類名
/*
	存取限定符
		public
		protected
		private 
	繼承方式
		public	公有繼承
		protected 保護繼承
		private	  私有繼承
*/
class Derive : public Base
{
public:
	Derive(int b)
		:mb(b), Base(b)
	{
		std::cout << "Derive::Derive()" << std::endl;
	}
	~Derive()
	{
		std::cout << "Derive::~Derive()" << std::endl;
	}
protected:
	int mb;
};

int main()
{
	Derive d(10);
	return 0;
}

基礎類別構造優先於派生類構造
先構造的後解構

6.繼承方式

1.存取限定符:

public: 任意位置
protected: 本類和子類
private: 本類

2.繼承方式:

public:公有繼承
protected:保護繼承
private:私有繼承

  • 基礎類別中不同存取限定符下的成員 以 不同的繼承方式繼承後 在派生類中存取限定?
    能在派生類的子類中存取才能 纔能證明是保護的
    private的也被繼承了,但不可存取
繼承方式 ↓ public protected private
public public protected 不可存取
protected protected protected 不可存取
private private private 不可存取

在这里插入图片描述

class Base
{
public:
	Base(){}
public:
	int ma;
protected:
	int mb;
private://本類類中存取
	int mc;
};
class Derive : private Base
{
public:
	void Show()
	{
		std::cout << mb << std::endl;
	}
};
class Derive2 : public Derive
{
public:
	void Show()
	{
		//std::cout << mb << std::endl;
	}
};
int main()
{
	//std::cout << sizeof(Derive) << std::endl;
	Derive d;
	Derive2 d2;
	//std::cout << d.mb << std::endl;   ---->類外存取
	d2.Show();
	d.Show();
	return 0;
}

3.面試題

  • 基礎類別中某個成員以某種繼承方式繼承後 派生類public
    1.成員在基礎類別的存取限定 public
    2.對應的繼承方式 public
  • 一個成員被繼承之後,不能在子類類中存取:private

7.類和類的關係

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8.同名函數的關係

1.過載

三要素 :
1.同名
2.不同參
3.同作用域

2.隱藏

派生類中同名的函數隱藏了基礎類別中所有的同名函數
1.同名
2.不同作用域(繼承層次)

class Base
{
public:
	Base(int a = 0)
		:ma(a)
	{}
	void Show()
	{
		std::cout << "Base::ma " << ma << std::endl;
	}
	void Show(int arg)
	{
		int tmp = ma + arg;
		std::cout << "Base:: Sum " << tmp << std::endl;
	}
protected:
	int ma;
}; 
class Derive : public Base
{
public:
	Derive(int b)
		:mb(b)
	{}
	void Show()
	{
		std::cout << "Derive::mb " << mb << std::endl;
	}
private:
	int mb;
};

int main()
{
	Base* pb = new Derive(10);//基礎類別指針 指向派生類物件
	Derive* pd = new Base(10);//派生類指針 指向基礎類別物件
	return 0;
}

int main()
{
	Derive d(10);
	d.Show(10);
	//d.Base::Show();
	return 0;
}

1.基礎類別和派生類的相互指向或參照

基礎類別指針 指向派生類物件
基礎類別參照 參照派生類物件
在这里插入图片描述

3.覆蓋(重寫)

派生類中同名同參的虛擬函式覆蓋基礎類別中同名同參的虛擬函式
1.同名同參
2.不同作用域(繼承層次)
3.虛擬函式

2.多型

1.多型:同一介面 不同形態 (同一函數名,不同功能)

1.靜多型 :
編譯確定函數的呼叫(call函數的入口地址)
2.動多型(預設爲動多型):
執行 確定函數的呼叫(call暫存器)
3.宏多型 :
預編譯 確定函數的呼叫
在这里插入图片描述

1.基礎類別中的虛擬函式

流程:虛擬函式爲了實現動多型,首先在編譯階段把函數的入口地址放在數據段(普通函數只放在符號表,虛擬函式符號表+數據段都放了),數據段是.rodata只讀數據段,虛擬函式的入口地址放在了只讀數據段中的虛擬函式表中,虛擬函式表被編譯鏈接,通過執行,載入指令和數據時,當做數據的一部分,載入到記憶體中,因此就可以在執行時找到虛擬函式的入口地址,實現動多型。
在这里插入图片描述
在这里插入图片描述

2.派生類中的虛擬函式

在这里插入图片描述
在这里插入图片描述
沒有物件就找不到入口地址
1.不依賴物件。
2.可以
3.不能取地址,直接替換。
4. cdcal呼叫約定,不依賴物件。
5. 和物件沒關係。
6. 可以
在这里插入图片描述
獲取內建單元對應的型別,只與定義點有關
自定義型別,typeid關鍵字列印時:先找到物件,然後找到虛擬函式指針型別,然後解除參照找到虛擬函式表,然後找到RTTI資訊
在这里插入图片描述

class Base
{
public:
	Base(int a = 10)
		:ma(a){}
	virtual void Show()
	{
		std::cout << "Base::ma " << ma << std::endl;
	}
	//virtual void Print()
	//{
	//	std::cout << "hello world!" << std::endl;
	//}
protected:
	int ma;
};
/*
	繼承層次  
		基礎類別中同名同參的函數是虛擬函式   
			派生類中同名同參的函數也是虛擬函式   
*/
class Derive : public Base
{
public:
	Derive(int b)
		:mb(b)
	{
	}
	virtual void Show()
	{
		std::cout << "Derive::mb " << std::endl;
	}

	//virtual void Test()
	//{
	//	std::cout << "Derive::Test()" << std::endl;
	//}
private:
	int mb;
};
/*
	動多型   
		執行階段  確定函數的呼叫  
		虛擬函式機制 機製提供支援 
*/
/*
	純虛擬函式  

*/
int main()
{
	std::cout << "Base::size : " << sizeof(Base) << std::endl;//4
	std::cout << "Derive::size : " << sizeof(Derive) << std::endl;
	Base* pb = new Derive(10);----》pb是Base*型別
	std::cout << "pb type: " << typeid(pb).name() << std::endl;
	/*
		*pb
			動態型別提取  RTTI (有虛擬函式後,*pb變成了class derive)
	*/
	std::cout << "*pb type: " << typeid(*pb).name() << std::endl;----*pb是Base型別
	pb->Show();//動多型   
	return 0;
}

在这里插入图片描述

2.判斷靜多型/動多型

1.靜多型

在这里插入图片描述

在这里插入图片描述

2.動多型

時機:指針呼叫虛擬函式+物件完整(虛擬函式的呼叫不在構造和解構中,在構造和解構內部,物件屬於半成品物件)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.多型的發生時機

在这里插入图片描述

4.分析

在这里插入图片描述
在这里插入图片描述
申請和釋放不是同一個地方,所以程式崩潰
在这里插入图片描述
在这里插入图片描述
基礎類別有虛解構函情況:(派生類的解構也會變成虛解構–》同名覆蓋)
在这里插入图片描述
在这里插入图片描述

5.虛解構

在这里插入图片描述

6.虛表的寫入時機

要把虛表寫進虛擬函式指針裏面,指針是物件生成後纔有的空間,而物件是在執行階段生成的
==問題:==是在開闢記憶體後寫入還是建構函式呼叫之後?
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.如果在建構函式之前:

將所有置爲0
在这里插入图片描述
在这里插入图片描述

2.如果在建構函式之後:

在这里插入图片描述

3.結論

程式出錯,因此虛表寫入的時機是:建構函式第一行程式碼執行之前

4.虛表的二次寫入

在这里插入图片描述
在这里插入图片描述
呼叫基礎類別時看不到派生類的虛擬函式表

在这里插入图片描述
在这里插入图片描述

7.純虛擬函式

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8.純虛擬函式筆試題(阿裡)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.解題思路

1.畫出對應的派生類的記憶體佈局和虛擬函式表
在这里插入图片描述

2.純虛擬函式的應用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
1.模板方式
在这里插入图片描述
在这里插入图片描述
2.在繼承層次中,允許基礎類別指針指向派生類物件
在这里插入图片描述
在这里插入图片描述

3.篩選

在这里插入图片描述

9.C++的四種類型轉換

C語言中是暴力轉化

1.const_cast:去除常性的轉換

去除常性轉換成int*型別
在这里插入图片描述

2.static_cast:系統覺得安全就會轉換,不安全就拒絕轉換

安全性更高
在这里插入图片描述
在这里插入图片描述

3.reinterpret_cast:類似於c語言的轉換,但常用於指針

4.dynamic_cast:用於 RTTI 資訊轉換

在这里插入图片描述

處理機制 機製

在这里插入图片描述

10.單繼承和多繼承

1.單繼承:繼承的時候只有唯一一個基礎類別

2.多繼承:派生類是由多個基礎類別派生出來的

1.菱形繼承

在这里插入图片描述

2.虛繼承的機制 機製:爲了消除記憶體重複(vbptr)

虛繼承關係中,基礎類別叫做虛基礎類別,ma就是虛基礎類別數據

  1. 把虛基礎類別數據放在作用域最下面 下麪
  2. 原來的位置被vbtable代替
  3. 虛基礎類別指針指向一個虛基礎類別表(表存在兩個偏移)
    作用:通過vbptr指針加上偏移,可以找到虛基礎類別數據在这里插入图片描述
    在这里插入图片描述

11.虛繼承

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.畫D的記憶體佈局(+說明構造解構順序)

  1. 非虛基礎類別的佈局優先於虛基礎類別
  2. 按照繼承順序處理虛基礎類別
  3. 虛基礎類別指針向內層合併(找到內層第一個)
  4. 平級不合併(不同作用域下的虛基礎類別指針不合併,但同一作用域下要合併)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    構造和解構的順序
  5. 虛基礎類別的構造優先於非虛基礎類別:EABCD
  6. 構造和解構相反:DCBAE

2.練習1:畫出D的記憶體佈局

  1. 非虛基礎類別的佈局優先於虛基礎類別
  2. 按照繼承順序處理虛基礎類別
  3. 虛基礎類別指針向內層合併(找到內層第一個)
  4. 平級不合併(不同作用域下的虛基礎類別指針不合併,但同一作用域下要合併)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    兩個vbptr是同一作用域下的虛基礎類別指針,需要合併(都是D作用域下的)
    在这里插入图片描述
    在这里插入图片描述
    構造解構順序:
    構造:E AB A C D(構造B之前要先構造A)
    解構:DCABAE

3.練習2:畫出D的記憶體佈局

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
構造解構順序:
構造:AC E B D(構造C之前要先構造A)
解構:

4.練習3:畫出D的記憶體佈局

在这里插入图片描述
在这里插入图片描述

構造解構順序:
構造:E AB AC D(構造C之前要先構造A)
解構:DCABAE

5.練習4:畫出Derive的記憶體佈局

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
vfptr的偏移:0-vfptr的位置
先處理虛擬函式,再處理虛繼承。
在这里插入图片描述
在这里插入图片描述

12.怎麼建立一個不能被繼承的類(面試題)

1.方式一:模擬單例模式

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.方式二:虛繼承

友元關係:

  1. 單向性
  2. 不能被繼承(父親的朋友不是兒子的朋友)
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述
繼承失敗

在这里插入图片描述

13.例外處理機制 機製

1.異常情況

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.拋出異常,會直接產生中斷

在这里插入图片描述
exception是一個類,exception(" …"); 叫做顯示生成臨時物件
throw後面放的就是一個物件
在这里插入图片描述

3.例外處理流程:try、catch、throw

try:包含可能發生異常的程式碼
catch:捕獲並處理異常
throw:拋出異常

1.try catch

err參照的就是 異常點 拋出的臨時物件
在这里插入图片描述

2.throw

如果throw的是一個double型別的數據,可以在main函數catch一個double err
在这里插入图片描述
在这里插入图片描述

3.catch all模組

在这里插入图片描述