最近兩次面試都問到了多態,我也不得不重視起來了,最近最大的收獲就是:基礎知識很重要,就算你很會寫代碼,但是面試官問你基礎知識答不上來的話,也很難被人賞識和錄用,所以還是要多補補基礎概念,這一篇就說多態。
之前第一篇提到過,多態是指同樣的消息被不同的對象接受時導致不同的行為。分四類:重載多態,強制多態,包含多態和參數多態。
多態從實現的角度分為 編譯時多態 運行時多態。不同處就是確定操作針對的具體對象的時間是編譯的時候還是運行的時候。
重載多態:
我們知道的普通函數及類的成員函數的重載都屬于重載多態。
函數的重載因為很常用相信一般都很熟悉了,就是指函數名相同而形參的個數和類型不同,編譯器調用的時候根據參數類型個數來判斷調用哪一個函數。
運算符的重載也是重載多態,比如我們定義了一個類Counter,希望這個類內部可以實現'+'的運算,比如Counter ?a,b,c; 初始化后可以有c=a+b;或類似的運算,那么我們需要對‘+’進行重載,給它以運算規則,來保證這句話能通過編譯。
(有5個運算符不可以被重載,分別是: . ?.* ? :: ? sizeof ? ??:)
運算符重載分兩種:作為成員函數重載 ?作為友元函數重載。
語法形式為:
? ? ? ? ? ?作為成員函數重載運算符: ? ? ? ? ? ? ? ?? | ? ? ? ? ? ?作為友元函數重載運算符: |
函數類型 operator 運算符(形參表){ ? ? ? ............; } | ? friend ?函數類型 operator 運算符(形參表){ ? ? ? ? ?? ? ? ? ? ?............; ?} |
?
?
?
?
?
之前也說過,友元函數其實是定義在函數外的,所以跟函數成員的區別就是友元函數需要兩個形參作為運算符前后的值。
比如我要重載上面的Counter 類型的+ - 運算:?
Counter operator +(Counter c2){
return Counter(this.number+c2.number);
}
//或者友元重載減號:
friend Counter operator -(Counter c1, Counter c2){
return Counter(c1.number-c2.number);
}
//調用時:
Counter a(5),b(3),c,d;
c=a+b;
d=a-b;
//都是可以通過的。
?
/
除了這種加減等常用的外,還有比如-- ++這種前置或后置運算符,比如我們要定義一個counter++; 那么運算符重載為Counter的成員函數,同時函數要帶有一個整數形參(int),它的作用就是區別說明它是后置的++ --。如果是前置,則沒有int形參。如下:
//前置++的重載
Counter Counter::operator ++(){
.........;
}
//后置++的重載
Counter Counter::operator ++(int){
...........;
}
//這樣,當出現++符號時,系統就可以根據位置知道調用哪個函數了:
Counter a(5);
a++; // 相當于調用了 operator ++(0);
++a; // 相當于調用了 operator ++();
強制多態:
就是指講一個變元的類型加以變化,符合一個函數或者操作的具體要求。
比如加法符號,在進行整型和浮點型的運算時,會先強制將整型轉為浮點型,然后再進行運算。
?
包含多態:
包含多態是研究類族中定義于不同類中的同名函數成員的多態行為,主要通過虛函數來實現.
虛函數必須是非靜態成員,經過多次派生之后,族類中可以實現運行過程中的多態。
一般虛函數成員聲明語法: |
virtual 函數類型 函數名(形參表){ ? ? ? .......................; } |
? 虛函數的聲明只能在類定義中的函數原型聲明時就寫清楚,而不能在寫函數體時才聲明。運行過程中要滿足三個規則:
1. 類之間要滿足類型兼容規則。
2. 聲明時虛函數。
3*. 要由成員函數來調用,或者是通過指針,引用來訪問虛函數。
?理論了解了之后,我們寫個小例子就懂了。
?以下是完整代碼:


#include <iostream>
using namespace std;
class Father{
public:
virtual void outputTest(){
cout<<"Father"<<endl;
}
};
class Son1:public Father{
public:
void outputTest(){
cout<<"Son1"<<endl;
}
};
class Son2:public Son1{
public:
void outputTest(){
cout<<"Son2"<<endl;
}
};
// 調用時
int main(){
Father f,*p;
Son1 s1;
Son2 s2;
p=&f;
p->outputTest();
p=&s1;
p->outputTest();
p=&s2;
p->outputTest();
}
?話不多說,我們截圖為證:
? 可見,通過使用virtual這個關鍵詞,我們實現了outputTest這個函數的包含多態。這三個類中,outputTest都是虛函數。
添一句話:如果子類中的一個函數fun,跟父類中的一個虛函數fun,函數名相同,參數表完全一致,返回值也相同,那么這函數就自動被判定為了虛函數。
虛析構函數:它的存在時為了防止某些情況下的空間泄露。所以虛構造函數是不允許存在的。同時,如果一個父類的析構函數是虛函數,那么子類的析構也同樣是虛函數。
下面就說說空間泄露的情況:假如我定義了一個父類Father,里面沒有數據成員,然后我定義了一個子類Son公有繼承父類,并且有一個私有數據成員*int t; Son的構造函數里有 t=new int(0);,析構函數里會delete t;,但是父類自然沒有這一句。這樣我們在定義對象的時候如果有這樣的情況: Father *f=new Son(); ?delete *f; 那么,啊~非常不意外的,可憐的t就被這樣無情的丟在了角落,不使用也不釋放。這樣如果在一個較大成程序里發生的話,很可能會出現內存不足的情況。
解決的辦法,就是在Father的析構函數前面加virtual,具體形式為: virtual ~Father(); 因為很簡單,就再給一次代碼好了,三分鐘搞定:


#include <iostream>
using namespace std;
class Father{
public:
Father(){};
virtual ~Father(){cout<<"Father delete;"<<endl;};
};
class Son:public Father{
public:
Son(){t=new int(0);};
~Son(){delete t; cout<<"Son delete;"<<endl;};
private:
int *t;
};
// 調用時
int main(){
Father *f=new Son();
delete f;
}
運行結果:
可見,子類的析構函數被調用了,這樣就避免了內存泄露帶來的危害。所以虛析構還是很有用的~!
除了上述兩種虛函數之外,我們還應該知道一種類 叫做:抽象類。
抽象類處于類的上一層,它本身無法被實例化,只能通過繼承機制,由抽象類生成非抽象的派生類,再對之實例化。
注意:帶有純虛函數的類就是抽象類。但是什么是純虛函數呢?
純虛函數就是在一個基類中聲明的虛函數,它在這個基類里面沒有具體要操作的內容,它存在的意義就是為了讓子類根據自己的需求對這個函數實現不同的接口。
純虛類的定義語法是: virtual 函數返回類型 函數名(參數表)=0; ?其實就是有了一個‘=0’。這樣就不需要給它定義函數體了,所以純虛函數是沒有函數體的。
?
參數多態:
參數多態,就是將程序所處理對象的類型參數化,使得一段程序可以用于處理多種不同類型的對象。
參數的多態,可以通過函數模板或類模板實現。
所謂函數模板,我們可以想一個例子:我們通常使用math.h里的很多函數比如絕對值函數abs()的時候,參數可以有int, double等不用類型的參數,那么如果為了每一種類型建立一個重載函數的話,就太麻煩了,因為函數體本身是相同的,所以我們就用一個模板來代替之,如下:
template <typename T>
T abs(T x){
return x<0?-x:x;
}
這樣,我們在調用的時候,如果給的參數是int,那么編譯器就會把typename里的T變為int,這個函數就是個返回int,參數為int的函數了,double等類型也一樣。
函數模板的語法定義: |
template <typename T> 或 template <class T> ? ?//表示T是一個類型名或者類名,可以更換為int 、 double、 某個類 等等 返回類型 ?函數名(參數表){? ? ? ? ? ? ? ?.........; }(注意返回類型不一定為T的~。) |
?
?
?
?
?
?
?
類模板:使用類模板可以為類聲明一種模式,是的類中某些數據成員、某些成員函數的參數、某些成員函數的返回值能取任意類型。
類模板的語法定義: | ?在類的外部定義成員函數的函數體時: |
?template <模板參數表> class 類名{ ? ? ? ...........; } | ?template <模板參數表> 返回類型 類名<模板參數表成員>::函數名(參數表){ ? ? ? ?........; } |
?
?
?
?
?
?
同樣,我們用一個小例子,一秒搞懂類模板:
#include <iostream>
using namespace std;
template<class Input, class Output>
class Test{
public:
Test(Input in){input=in;}
Output fun();
private:
Input input;
Output output;
};
// 類之外定義函數時的格式:
template<class Input, class Output>
Output Test<Input,Output>::fun(){
return input>0?input:-input;
}
int main(){
Test<int,int> test(-30);
cout<<test.fun()<<endl;
}
查看輸出:
?成功~! 呵呵 雖然沒有含金量,但是該用到的格式這個小程序都用到了,不錯吧~