1.多態性
- 多態性定義:向不同對象發送同一個消息,不同的對象在接收時會產生不同的行爲(即方法)。也就是說,每個對象可以用自己的方式去響應共同的消息,就是調用函數,不同的行爲就是指不同的實現,即執行不相同的函數。
- 例如:老師給你和你媽媽(不同對象)同時發送了你的期末考試成績(消息),對於這一信息,你在思考爲何會錯(可能你在思考回家會不會捱打),你媽媽在思考等你回家怎麼揍你(不同行爲)
- 多態的另一種理解:編譯時一種狀態,運行時一種狀態
- 程序的多態性:
- 編譯時多態:又叫靜態多態,程序在編譯階段就能決定調用哪個函數
- 運行時多態:又叫動態多態, 程序在編譯階段不確定調用哪個函數,而是在程序運行過程中才動態地確定操作所針對的對象
2.虛函數
思考下列程序的缺點
#include <iostream>
using namespace std;
class A{
protected:
int x;
int y;
int z;
public:
A(int x,int y,int z){
this->x = x;
this->y = y;
this->z = z;
}
void display(){
cout<<"x = "<<x<<",y = "<<y<<",z = "<<z<<endl;
}
};
class B : public A{
protected:
int m;
int n;
public:
B(int x,int y,int z,int m,int n):A(x,y,z){
this->m = m;
this->n = n;
}
void display(){
cout<<"x = "<<x<<",y = "<<y<<",z = "<<z<<",m = "<<m<<",n = "<<n<<endl;
}
};
int main(){
A a(1,2,3);
B b(4,5,6,7,8);
A *p;
p = &a;
p->display(); // 輸出:1 2 3
p = &b;
p->display(); //輸出: 4 5 6
return 0;
}
父類和子類同時都有display函數,用於輸出本類的屬性值,但是用父類型指針分別指向父類對象和子類對象並通過指針間接調用display函數,可以發現:無論指向的是父類對象還是子類對象,調用display函數都是輸出x,y,z的值,調用子對象的display輸出的也是x,y,z
Q:如何消除上述問題呢?
A:將父類中的display函數設置爲虛函數!!!
virtual void display(){
cout<<"x = "<<x<<",y = "<<y<<",z = "<<z<<endl;
}
這樣編譯運行之後,程序輸出變成了這樣:
此時用父類型指針指向子類對象並調用display函數,不僅僅輸出了從父類繼承來的屬性值,還輸出了子類特有的屬性值
上述虛函數的應用,使父類指針指向不同子類(不同對象),調用同一函數p->display();(同一信息),產生了不同的輸出(不同行爲),這就是多態性
- 虛函數解決了繼承類中同名方法重載的問題,重載的方法體不同,如何在程序運行時,讓程序知道應該調用哪一個類中的重載方法
#include <iostream>
using namespace std;
class Animal{
public:
virtual void jiao(){}
};
class Cat : public Animal{
public:
void jiao(){
cout<<"miao miao miao~~~~~~~"<<endl;
}
};
class Dog : public Animal{
public:
void jiao(){
cout<<"wang wang wang~~~~~~~"<<endl;
}
};
int main(){
Animal *animal; //定義父類指針
Cat cat; //定義Cat對象
Dog dog; //定義Dog對象
animal = &cat;
animal->jiao();
animal = &dog;
animal->jiao();
return 0;
}
輸出:
調用同一函數產生不同輸出,這個例子對於多態理解會更爲深刻
看完上面的代碼,你可能會有以下疑問:
Q:爲什麼不直接使用子類對象調用display方法呢,如:cat.jiao();和dog.jiao(),偏偏使用父類指針來調用呢?
A:其實這是因爲面向對象編程要求對程序進行解耦合,提高程序的可擴展性,多態機制就是非常典型的面向抽象編程,不要面向具體編程,上述代碼中Animal就是抽象,子類Cat和Dog就是具體,面向父類Animal進行編程,所以使用父類型指針,子類對象你可以隨便增加刪除,只需要修改父類指針指向就可以,而cat.jiao()這種調用方法就是面向具體編程。
總結:面向抽象編程,不要面向具體編程
虛析構函數
#include <iostream>
using namespace std;
class A{
public:
virtual ~A(){
cout<<"執行A類對象析構函數!"<<endl;
}
};
class B : public A{
public:
~B(){
cout<<"執行A類子類B類對象析構函數!"<<endl;
}
};
class C : public A{
public:
~C(){
cout<<"執行A類子類C類對象析構函數!"<<endl;
}
};
int main(){
A *pb,*pc;
pb = new B;
pc = new C;
delete pb;
delete pc;
return 0;
}
輸出:
- 如果不將父類析構函數寫成虛函數,因爲子類對象是new出來的,則用delete撤銷對象時,系統只會執行基類的析構函數,而不指向子類的析構函數
3.抽象類
瞭解抽象類之前我們首先需要了解一個概念:純虛函數
- 純虛函數:此函數只有函數名、返回值、形參列表,並沒有函數體,它的函數體留給子類根據需要去實現
- 純虛函數定義格式:virtual 返回值類型 函數名(參數列表) const = 0;
- 例如:求圖形面積函數:virtual float area() const = 0;這裏就是將area聲明爲一個純虛函數
- 注意:
- ①純虛函數沒有函數體{ }
- ②const = 0只是爲了告訴編譯系統此函數是純虛函數
- ③純虛函數末尾有分號(;)
- ④該純虛函數如果沒有在子類中實現,則該函數在子類中仍然是純虛函數
代碼功能:在Animal類中定義純虛函數jiao,子類實現
class Animal{
public:
virtual void jiao() const = 0;
};
class Cat : public Animal{
public:
void jiao() const{
cout<<"miao miao miao~~~~~~~"<<endl;
}
};
class Dog : public Animal{
public:
void jiao() const{
cout<<"wang wang wang~~~~~~~"<<endl;
}
};
int main(){
Animal *animal;
animal = new Cat;
animal->jiao();
animal = new Dog;
animal->jiao();
delete animal;
return 0;
}
- 抽象類:一種不用來定義對象,只用來繼承的類
- 抽象類作用:作爲公共基類去建立其他的派生類
- 凡是包含純虛函數的類都被稱爲抽象類
- 因爲純虛函數在被實現之前是無法被調用的,所以抽象類無法實例化(即無法有抽象了來創建抽象類對象)
- 抽象類是爲某一類族提供公共接口
- 動態關聯:定義父類型指針,通過指向不同子類對象來調用方法,在運行階段才知道應該調用哪個子類中的方法,稱爲動態關聯
- 靜態關聯:定義各個子類對象,通過對象名來調用方法,在編譯階段編譯器就知道應該調用哪裏的方法,稱爲靜態關聯。
#include <iostream>
using namespace std;
class Animal{
public:
virtual void eat() const{} //虛函數
virtual void sleep() const{} //虛函數
virtual void jiao() const = 0; //純虛函數
};
class Dog : public Animal {
public: //實現抽象類中的方法
void eat() const{
cout<<"I'm eating now!"<<endl;
}
void sleep() const{
cout<<"I'm sleeping now!"<<endl;
}
virtual void jiao() const{
cout<<"wang wang wang !"<<endl;
}
};
int main(){
Animal *animal;
animal = new Dog;
Dog dog;
animal->eat(); //動態關聯
dog.sleep(); //靜態關聯
animal->jiao(); //動態關聯
delete animal;
return 0;
}
通過這個程序就可以對抽象類、純虛方法、虛函數、動態關聯、靜態關聯有了更好的理解!