文章目錄
多態
多態:通俗說就是多種形態,具體點就是去完成某個行爲,當不同的對象去完成時會產生出不同的狀態。
1. 多態的定義及實現
1.1 多態構成條件
多態是在不同繼承關係的類對象,去調用同一函數,產生了不同的行爲。比如Student繼承了Person。Person對象買票全價,Student對象買票半價。
構成多態的兩個條件
- 1.必須通過基類的指針或者引用調用虛函數;
- 2.被調用的函數必須是虛函數,且派生類必須對基類的虛函數進行重寫;
1.2 虛函數
虛函數:被 virtual 修飾的類成員函數。
class Person {
public:
virtual void BuyTicket() { cout << "買票-全價" << endl;}
};
1.3 虛函數的重寫
虛函數的重寫(覆蓋):派生類中有一個跟基類完全相同的虛函數(即派生類虛函數與基類虛函數的返回值類
型、函數名字、參數列表完全相同),稱子類的虛函數重寫了基類的虛函數。
class Person {
public:
virtual void BuyTicket() { cout << "買票-全價" << endl; }
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "買票-半價" << endl; }
/*注意:在重寫基類虛函數時,派生類的虛函數在不加virtual關鍵字時,雖然也可以構成重寫(因爲繼
承後基類的虛函數被繼承下來了在派生類依舊保持虛函數屬性),但是該種寫法不是很規範,不建議這樣使用
*/
/*void BuyTicket() { cout << "買票-半價" << endl; }*/
};
void Func(Person& p) //必須是指針或者引用
{ p.BuyTicket(); }
int main()
{
Person ps;
Student st;
Func(ps);
Func(st);
return 0; }
虛函數重寫的兩個例外
- 1、協變(基類與派生類虛函數返回值類型不同)
派生類重寫基類虛函數時,與基類虛函數返回值類型不同。即基類虛函數返回基類對象的指針或者引用,派生類虛函數返回派生類對象的指針或者引用時,稱爲協變。
class A{};
class B : public A {};
class Person {
public:
virtual A* f() {return new A;}
};
class Student : public Person {
public:
virtual B* f() {return new B;}
};
- 2、析構函數的重寫(基類與派生類析構函數的名字不同)
如果基類的析構函數爲虛函數,此時派生類析構函數只要定義,無論是否加virtual關鍵字,都與基類的析構函數構成重寫,雖然基類與派生類析構函數名字不同。雖然函數名不相同,看起來違背了重寫的規則,其實不然,這裏可以理解爲編譯器對析構函數的名稱做了特殊處理,編譯後析構函數的名稱統一處理成destructor。
只有派生類Student的析構函數重寫了Person的析構函數,下面的delete對象調用析構函數,才能構成多態,才能保證p1和p2指向的對象正確的調用析構函數。
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;
return 0;
}
下列代碼輸出的結果是什麼?
class A {
public:
virtual void func(int val = 1){ std::cout<<"A->"<< val <<std::endl;}
virtual void test(){ func();}
};
class B : public A {
public:
void func(int val=0){ std::cout<<"B->"<< val <<std::endl; }
};
int main(int argc ,char* argv[])
{
B*p = new B;
p->test();
return 0; }
答案:: B->1,B是A的重寫,但是缺省參數用的仍然是A中的。
1.4 c++11中的 override 和 final
1、final:修飾虛函數,表示該虛函數不能再被繼承
class Car
{
public:
virtual void Drive() final {}
};
class Benz :public Car
{
public:
virtual void Drive() {cout << "Benz-舒適" << endl;}
};
2、 override: 檢查派生類虛函數是否重寫了基類某個虛函數,如果沒有重寫編譯報錯。
class Car{
public:
virtual void Drive(){}
};
class Benz :public Car {
public:
virtual void Drive() override {cout << "Benz-舒適" << endl;}
}
1.5 重載、覆蓋(重寫)、隱藏(重定義)的區別
2、抽象類
2.1 純虛函數
在虛函數的後面寫上 =0 ,則這個函數爲純虛函數。包含純虛函數的類叫做抽象類(也叫接口類),抽象類不能實例化出對象。派生類繼承後也不能實例化出對象,只有重寫純虛函數,派生類才能實例化出對象。純虛函數規範了派生類必須重寫,另外純虛函數更體現出了接口繼承。
純虛函數的作用
- 1、強制子類去完成重寫;
- 2、表示抽象的類型。抽象就是現實中沒有對應的實體。
class Car
{
public:
virtual void Drive() = 0; //不需要實現,純虛函數
};
//還是一個抽象類
class Benz :public Car
{
public:
virtual void Drive()
{
cout << "Benz-舒適" << endl;
}
};
int main()
{
Car car;
Benz bz;
return 0;
}
2.2 接口繼承和實現繼承
普通函數的繼承是一種實現繼承,派生類繼承了基類函數,可以使用函數,繼承的是函數的實現。虛函數的繼承是一種接口繼承,派生類繼承的是基類虛函數的接口,目的是爲了重寫,達成多態,繼承的是接口。所以如果不實現多態,不要把函數定義成虛函數。