繼承可以實現代碼複用,但它的主要用處是可以實現多態。
多態
一、概念
多態是指完成某個行爲時,當不同的對象去完成會產生出不同的狀態
舉個例子:買票的時候,成人票全價,兒童票半價
簡單的說:就是用基類的引用指向子類的對象。
二、分類
①動態多態:在程序運行時確定函數行爲。(動態綁定,晚綁定)
- 虛函數的重寫(覆蓋):派生類中有一個跟基類的完全相同虛函數,我們就稱子類的虛函數重寫了基類的虛函數,完全相同是指:函數名、參數、返回值都相同。(返回值無要求)
②靜態多態:在編譯期間確定函數行爲。(靜態綁定,早綁定)
靜態多態有兩種實現方式:
- 函數重載:包括普通函數的重載和成員函數的重載。
- 函數模板的使用。
三、實現條件
多態的實現:多態是在不同繼承關係的類對象,去調用同一函數,產生了不同的行爲。
在C++繼承中,要構成多態必須滿足兩個條件:
- 基類中必須包含虛函數,且在派生類中必須對基類的虛函數進行重寫。
- 調用虛函數的對象必須是基類的指針或者引用。
虛函數:在類的成員函數的前面加virtual關鍵字。
class A { public: virtual void Test() { } };
虛函數的重寫:派生類中有一個跟基類的完全相同虛函數,我們就稱派生類的虛函數重寫了基類的虛函數。
(完全相同是指:函數名、參數列表、返回值類型都相同。另外虛函數的重寫也叫作虛函數的覆蓋)
(例外:協變、析構)
class Person { public: virtual void BuyTicket() { cout << "買票-全價" << endl; } }; class Student : public Person { public: virtual void BuyTicket() { cout << "買票-半價" << endl; } }; void Test(Person& p) { p.BuyTicket(); } int main() { Person ps; Student st; Test(ps); Test(st); system("pause"); return 0; }
虛函數重寫有一個例外:
協變:重寫的虛函數的返回值可以不同,但是必須分別是基類指針和派生類指針或者基類引用和派生類引用。
析構函數的重寫問題:
基類中的析構函數如果是虛函數,那麼派生類的析構函數就重寫了基類的析構函數。這裏他們的函數名不相同,看起來違背了重寫的規則,但實際上,編譯器對析構函數的名稱做了特殊處理,編譯後析構函數的名稱統一處理成destructor,這也說明:基類的析構函數最好寫成虛函數。
class Person { public: virtual ~Person() { cout << "~Person()" << endl; } }; class Student : public Person { public: virtual ~Student() { cout << "~Student()" << endl; } }; // 只有派生類Student的析構函數重寫了Person的析構函數,下面的delete對象調用析構函數, // 才能構成多態,才能保證p1和p2指向的對象正確的調用析構函數。 int main() { Person* p1 = new Person; Person* p2 = new Student; delete p1; delete p2; system("pause"); return 0; }
虛函數爲什麼不能是static:
- 靜態成員函數,可以不通過對象來調用,即沒有隱藏的this指針
- 虛函數一定要通過對象來調用,即有隱藏的this指針
抽象類
接口繼承和實現繼承:
- 普通函數的繼承是一種實現繼承,派生類繼承了基類函數,可以使用函數,繼承的是函數的實現。
- 虛函數的繼承是一種接口繼承,派生類繼承的是基類虛函數的接口,目的是爲了重寫,達成多態,繼承的是接口。
所以如果不實現多態,不要把函數定義成虛函數。
在虛函數的後面寫上 =0 ,則這個函數爲純虛函數。包含純虛函數的類叫做抽象類(也叫接口類),抽象類不能實例化出對象。派生類繼承後也不能實例化出對象,只有重寫純虛函數,派生類才能實例化出對象。純虛函數規範了派生類必須重寫,另外純虛函數更體現出了接口繼承。
舉個例子:
class Person
{
public:
virtual void BuyTicket() = 0;
};
class Student :public Person
{
public:
virtual void BuyTicket()
{
cout << "買票-半價" << endl;
}
};
class Adult :public Person
{
public:
virtual void BuyTicket()
{
cout << "買票-全價" << endl;
}
};
void Test()
{
Person* pStudent = new Student;
pStudent->BuyTicket();
Person* pAdult = new Adult;
pAdult->BuyTicket();
}
int main()
{
Test();
system("pause");
return 0;
}
此外,C++11提供 final 和 override 來修飾虛函數:
- final:使用final修飾虛函數,表明該虛函數在後續子類中不能被重寫,一般修飾在派生類的虛函數中。
- override:幫助檢測派生類中該虛函數是否是對基類中某個虛函數的重寫,只能限制派生類的虛函數(基類中使用無意義)
實際中我們建議多使用純虛函數+ overrid的方式來強制重寫虛函數,因爲虛函數的意義就是實現多態,如果沒有重寫,虛函數就沒有意義。