多態
多態在維基百科)中的定義如下:
在編程語言和類型論中,多態(英語:polymorphism)指為不同數據類型的實體提供統一的接口。多態類型(英語:polymorphic type)可以將自身所支持的操作套用到其它類型的值上。
多態按照字面意思來理解,就是多種形態,當我們定義的類之間存在層次的結構時,并且類之間是通過繼承來關聯的時候,就會用到多態,多態意味著調用成員函數的時候,會根據函數的調用的對象的類型來執行不同的函數。
試想下面的情景,在一個全球性聊天軟件中,對于不同國籍的人來說,系統檢測到當前用戶的國籍即輸出當前用戶所說的語言。我們設計出如下代碼:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define R register
#define LL longlong
#define pi 3.141
#define INF 1400000000
usingnamespace std;
classPerson{
public:
Person(){
}
void speak(){
printf("Speakn");
}
};
classChinese:publicPerson{
public:
Chinese(){
}
void speak(){
printf("Speak Chinesen");
}
};
classEnglish:publicPerson{
public:
English(){
}
void speak(){
printf("Speak Englishn");
}
};
int main(){
ChinesePerson_Chinese;
EnglishPerson_English;
Person_Chinese.speak();
Person_English.speak();
return0;
}
運行上面的代碼,輸出的結果為:
Speak Chinese
Speak English
我們換一個思路,如果這三個人都是人那么我需要在不同國籍的人后面輸出他們所說的語言:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define R register
#define LL longlong
#define pi 3.141
#define INF 1400000000
usingnamespace std;
classPerson{
public:
intType;
public:
Person(){
Type=0;
}
void speak(){
printf("Speakn");
}
};
classChinese:publicPerson{
public:
Chinese(){
Type=1;
}
void speak(){
printf("Speak Chinesen");
}
};
classEnglish:publicPerson{
public:
English(){
Type=2;
}
void speak(){
printf("Speak Englishn");
}
};
int main(){
ChineseChinese_Person_1;
ChineseChinese_Person_2;
EnglishEnglish_Person_1;
Person* people[3];
people[0]=&Chinese_Person_1;
people[1]=&Chinese_Person_2;
people[2]=&English_Person_1;
for(R int i =0; i <3;++i){
printf("Person%d:", i +1);
if(people[i]->Type==1){
Chinese* person =(Chinese*)people[i];
person->speak();
}
elseif(people[i]->Type==2){
English* person =(English*)people[i];
person->speak();
}
}
return0;
}
嘗試編譯這段代碼輸出了同樣的結果,但是如果這樣寫代碼,寫出來的代碼將會非常冗余,我們可以嘗試在父類的函數前加上virtual關鍵字,從而實現相同的功能。
考慮下為什么不加關鍵字,程序的輸出結果為:
Person1:Speak
Person2:Speak
Person3:Speak
導致程序錯誤輸出的原因是,調用函數speak被編譯器設置為基類中的版本,這就是所謂的靜態多態,/靜態鏈接,也就是說函數調用在程序執行前就已經準備好了,這種情況被稱之為早綁定,因為函數在程序編譯的時候就已經被設置好了,當我們使用virtual關鍵字的時候,編譯器看的是指針的內容,而不是指針的類型,因此將子類的地址提取出來會調用各自的speak函數。
正如上所示,每一個子類都有一個函數speak獨立實現,這就是多態的一般的使用方式,有了多態就會有多個不同的類,并且可以實現同一個名稱但是不同作用的函數,甚至函數的參數可以完全相同。改進后的程序如下所示:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define R register
#define LL longlong
#define pi 3.141
#define INF 1400000000
usingnamespace std;
classPerson{
public:
intType;
public:
Person(){
Type=0;
}
void speak(){
printf("Speakn");
}
};
classChinese:publicPerson{
public:
Chinese(){
Type=1;
}
void speak(){
printf("Speak Chinesen");
}
};
classEnglish:publicPerson{
public:
English(){
Type=2;
}
void speak(){
printf("Speak Englishn");
}
};
int main(){
ChineseChinese_Person_1;
ChineseChinese_Person_2;
EnglishEnglish_Person_1;
Person* people[3];
people[0]=&Chinese_Person_1;
people[1]=&Chinese_Person_2;
people[2]=&English_Person_1;
for(R int i =0; i <3;++i){
printf("Person%d:", i +1);
people[i]->speak();
}
return0;
}
虛函數
虛函數是指在父類中定義的使用關鍵字virtual聲明的函數,在子類中需要重新定義父類中定義的虛函數的時候程序會告訴編譯器不要靜態鏈接到這個函數。
有些時候,我們編寫程序,需要的是在程序中的任意點可以根據所調用的對象的類型來選擇我們需要調用的函數,這種操作被稱為動態鏈接或者后期綁定。
純虛函數
如果我們在進行程序設計的時候,可能在父類中無法或者不需要對虛函數給出一個有意義的實現,那么此時就需要用到純虛函數,我們可以將父類中的虛函數改為如下形式:
classPerson{
public:
intType;
public:
Person(){
Type=0;
}
virtualvoid speak()=0;
};
通過上面的方法,我們告訴編譯器這個函數沒有主體,就是一個純虛函數。
動態多態的條件
1.父類中必須包含虛函數,并且子類中一定要對父類中的虛函數進行重寫。
2.通過基類對象的指針或者引用進行調用虛函數
重寫
1.基類中將要被重寫的函數必須是虛函數
2.基類的派生類中的虛函數的原型必須保持一致,協變函數和析構函數(父類和子類的析構哈桑農戶不同)除外
3.訪問限定符不同
協變函數 :父類(子類)的虛函數返回父類(子類)的指針或者引用
不能被定義為虛函數的子類
1.友元函數,不是類的成員函數
2.全局函數
3.靜態成員函數,沒有this指針
4.構造函數,拷貝構造函數,賦值運算符重載(賦值運算符可以作為虛函數但是不建議作為虛函數)
總結一些要點
包含純虛函數的類被稱為抽象類(也成為接口類),抽象類不能實例化出對象,純虛函數在子類中重新定義之后,子類才能實例化出對象,純虛函數一定要被繼承,否則純虛函數的存在沒有任何意義,純虛函數一定沒有定義,純虛函數的存在就是用來規范子類的行為。
對于虛函數來說,父類和子類都有各自的版本,由多態方式調用的時候動態綁定。
實現了純虛函數的子類,這個純虛函數在子類中就變成了虛函數,子類的子類可以覆蓋整個虛函數,由多態的方式調用的時候進行動態綁定。
在有動態分配的堆上內存的時候,析構函數必須是虛函數,但沒有必要是純虛函數。
友元函數不等于成員函數,只有成員函數才可以是虛函數,因此友元函數不能是虛函數,但是可以通過讓友元函數調用虛擬成員函數來解決友元的虛擬問題。
析構函數應該是虛函數,將調用相應的對象類型的析構函數,因此如果指針指向的是子類的對象,將調用子類的洗后函數,然后再自動調用父類的析構函數。
評論