天天看點

c++多态(動态)多态概念

C++多态總結

  • (動态)多态概念
    • (動态)多态底層
      • (動态)多态優點
      • 純虛函數和抽象類
      • 虛析構和純虛析構

多态是C++面向對象三大特性封裝、繼承、多态之一

多态分為兩類

  • 靜态多态: 函數重載 和 運算符重載屬于靜态多态,複用函數名。(和下面多态需要重寫父類函數不同)
  • 動态多态: 派生類和虛函數實作運作時多态(父類對應屬性,子類同樣也對應有屬性,需要用到多态情況下,在父類屬性添加virtual修飾,子類可加可不加。)

靜态多态和動态多态差別:

  • 靜态多态的函數位址早綁定 - 編譯階段确定函數位址
  • 動态多态的函數位址晚綁定 - 運作階段确定函數位址

(動态)多态概念

父類動物在叫的函數;子類小狗在叫的函數,子類小貓在叫的函數。

//我們希望傳入什麼對象,那麼就調用什麼對象的函數

多态滿足條件

  • 有繼承關系
  • 子類重寫父類中的虛函數(不是重載)

多态使用條件

  • 父類指針或引用指向子類對象

重寫:函數傳回值類型 函數名 參數清單 完全一緻稱為重寫

class Animal
{
public:
	//Speak函數就是虛函數
	//函數前面加上virtual關鍵字,變成虛函數,那麼編譯器在編譯的時候就不能确定函數調用了。
	virtual void speak()
	{
		cout << "動物在說話" << endl;
	}
};

class Cat :public Animal 
{
public:
	virtual void speak() //子類重寫父類函數
	{
		cout << "小貓在說話" << endl;
	}
};

class Dog :public Animal
{
public:

	virtual void speak()  //子類重寫父類函數
	{
		cout << "小狗在說話" << endl;
	}

};
//我們希望傳入什麼對象,那麼就調用什麼對象的函數
//如果函數位址在編譯階段就能确定,那麼靜态聯編
//如果函數位址在運作階段才能确定,就是動态聯編

void DoSpeak(Animal & animal)
{
	animal.speak();
}
//
//多态滿足條件: 
//1、有繼承關系
//2、子類重寫父類中的虛函數
//多态使用:
//父類指針或引用指向子類對象

void test01()
{
	Cat cat;
	DoSpeak(cat);


	Dog dog;
	DoSpeak(dog);
}


int main() {

	test01();

	system("pause");

	return 0;
}
           

(動态)多态底層

上述代碼的Animal類的記憶體占4位元組, 而去掉virtual變成1位元組。則有virtual說明變成了vfptr(virtual function pointer 虛函數(表)指針),指向vftable(virtual function table虛函數表(裡面存放了父類函數的入口位址))。

1、如果沒有父類指針或引用指向子類對象**,則沒有多态産生。cat類vfptr指向vftable會繼承存放父類的函數表一個函數的入口位址。

2、當父類指針或引用指向子類對象**,cat類的vfptr指向vftable會存放自己virtual的對應重寫的函數入口位址,替換掉1所說的函數入口位址。(子類函數重寫加入virtual會比較好了解)

(動态)多态優點

步驟:

1、通過給一個父類,裡面的虛函數(virtual修飾)(下面會講純虛函數,此時父類僅僅隻是一個抽象類,需要子類去重寫純虛函數實作功能。)沒有寫任何東西,在裡面空着。

2、

(1)寫一個要實作功能的子類,通過使用繼承父類(抽象類)。.

(2)父類引用、指針指向子類的對象。(1、可父類指針指向到new 子類在堆區開辟空間 ,記得記憶體釋放 delete掉)(2、也可以使用引用指向一個子類對象,要建立子類對象,不用new且不用delete)

(3)子類虛重寫對應父類的虛函數。

(4)利用多态來實作子類的函數,進行對整個程式架構的擴充(也可有利于多人協助,維護,可讀性高,結構清晰)。

  • 代碼組織結構清晰
  • 可讀性強
  • 利于前期和後期的擴充以及維護
  • 總結:C++開發提倡利用多态設計程式架構,因為多态優點很多

純虛函數和抽象類

在多态中,通常父類中虛函數的實作是毫無意義的,主要都是子類重寫的該虛函數内容。

是以可以将虛函數改為純虛函數

純虛函數文法:

virtual 傳回值類型 函數名 (參數清單)= 0 ;

當類中有了純虛函數,這個類也稱為抽象類

抽象類特點:

  • 無法執行個體化對象
  • 子類必須重寫抽象類中的純虛函數,否則也屬于抽象類

虛析構和純虛析構

多态使用時,如果子類中有屬性開辟到堆區,那麼父類指針在釋放時無法調用到子類的析構代碼。(因為子類在堆區開辟了空間,而父類隻能delete父類自己的空間,而無法進入到子類去析構delete子類的空間)

解決方式:将父類中的析構函數改為虛析構或者純虛析構

虛析構和純虛析構共性:

  • 可以解決父類指針釋放子類對象
  • 都需要有具體的函數實作

虛析構和純虛析構差別:

  • 如果是純虛析構,該類屬于抽象類,無法執行個體化對象

1、虛析構文法:

virtual ~類名(){}

2、純虛析構文法:

virtual ~類名() = 0;

類名::~類名(){}

類外實作。

代碼

class Animal {
public:

	Animal()
	{
		cout << "Animal 構造函數調用!" << endl;
	}
	virtual void Speak() = 0;

	//析構函數加上virtual關鍵字,變成虛析構函數
	//virtual ~Animal()
	//{
	//	cout << "Animal虛析構函數調用!" << endl;
	//}


	virtual ~Animal() = 0;
};

Animal::~Animal()
{
	cout << "Animal 純虛析構函數調用!" << endl;
}

//和包含普通純虛函數的類一樣,包含了純虛析構函數的類也是一個抽象類。不能夠被執行個體化。

class Cat : public Animal {
public:
	Cat(string name)
	{
		cout << "Cat構造函數調用!" << endl;
		m_Name = new string(name);
	}
	virtual void Speak()
	{
		cout << *m_Name <<  "小貓在說話!" << endl;
	}
	~Cat()
	{
		cout << "Cat析構函數調用!" << endl;
		if (this->m_Name != NULL) {
			delete m_Name;
			m_Name = NULL;
		}
	}

public:
	string *m_Name;
};

void test01()
{
	Animal *animal = new Cat("Tom");
	animal->Speak();

	//通過父類指針去釋放,會導緻子類對象可能清理不幹淨,造成記憶體洩漏
	//怎麼解決?給基類增加一個虛析構函數
	//虛析構函數就是用來解決通過父類指針釋放子類對象
	delete animal;
}

int main() {

	test01();

	system("pause");

	return 0;
}
           

總結:

​ 1. 虛析構或純虛析構就是用來解決通過父類指針釋放子類對象(因為子類屬性在堆區開辟了空間,而父類隻能delete父類自己的空間,而無法進入到子類去析構delete子類屬性的空間)

​ 2. 如果子類中沒有堆區資料,可以不寫為虛析構或純虛析構

​ 3. 擁有純虛析構函數的類也屬于抽象類

繼續閱讀