C++多態(基礎)

繼承可以實現代碼複用,但它的主要用處是可以實現多態。

多態

一、概念

        多態是指完成某個行爲時,當不同的對象去完成會產生出不同的狀態

        舉個例子:買票的時候,成人票全價,兒童票半價

        簡單的說:就是用基類的引用指向子類的對象。

、分類

①動態多態:在程序運行時確定函數行爲。(動態綁定,晚綁定)

  • 虛函數的重寫(覆蓋):派生類中有一個跟基類的完全相同虛函數,我們就稱子類的虛函數重寫了基類的虛函數,完全相同是指:函數名、參數、返回值都相同。(返回值無要求)

②靜態多態:在編譯期間確定函數行爲。(靜態綁定,早綁定)

    靜態多態有兩種實現方式:

  • 函數重載:包括普通函數的重載和成員函數的重載。
  • 函數模板的使用。

 

三、實現條件

        多態的實現:多態是在不同繼承關係的類對象,去調用同一函數,產生了不同的行爲。

        在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的方式來強制重寫虛函數,因爲虛函數的意義就是實現多態,如果沒有重寫,虛函數就沒有意義。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章