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. 擁有純虛析構函數的類也屬于抽象類