標籤:重載函數 過程 err ati 數列 特徵 類構造 初始 TE
1 函數的重載、重寫(重定義)、函數覆蓋及隱藏
其實函數重載與函數重寫、函數覆蓋和函數隱藏不是一個層面上的概念。前者是同一個類內,或者同一個函數範圍內,同名不同參數列表的函數之間的關係。而後三者是基類和衍生類別函數不同情況下的關係。
1.1 函數重載
正如上文說的函數的重載是指類內部,同名不同參數列表函數之間的關係。如下:
void show();void show(int);void show(int , double);
以上是多個同名參數列表不同的函數,這種情況就是函數重載。不同的函數傳回值不作為判斷函數重載的依據,比如如下兩個函數會被判定為函數重定義。
void show();int show();
編譯器錯誤:“錯誤(活動) E0311 無法重載僅按傳回型別區分的函數”。
不過函數重載要注意的是有預設參數和類型的隱式轉換造成的問題,這裡需要瞭解一下編譯器是如何選擇使用那個版本的函數的。其大致過程是
- 第一步,建立候選函數列表。其中包含與被調用函數的名稱相同的函數
- 第二步,使用候選函數列表建立可行函數列表,這些都是參數數目正確的函數,為此有一個隱式轉換序列,其中包括實參數類型與相應的形參類型完全符合的情況。例如,使用float參數的函數調用可以將該參數轉換為double,從而與double形參匹配。
- 第三步,確定是否有最佳的可行函數。如果有,則使用它,否則該函數調用出錯(沒有匹配項或者有多個匹配項都出錯)
所以再重載函數的時候要注意有預設參數值和隱式轉換,如下:
#include <iostream>void show(){ std::cout << "無參數" << std::endl;}void show(int n = 1){ std::cout << "有參數" << std::endl;}void show(double d){ std::cout << "有參數 double" << std::endl;}void show(double & d){ std::cout << "有參數 double 引用" << std::endl;}int mian(){ show(); //E0308 有多個 重載函數 "show" 執行個體與參數列表匹配: double d = 10; show(d); //E0308 有多個 重載函數 "show" 執行個體與參數列表匹配}
對於有預設參數值的情況不必多說,對於隱式轉換則要注意在匹配的第三步編譯器確認那些是最佳的。它查看為使函數調用參數與可行的時候選函數的參數匹配所需要進行的轉換。通常從最佳到最差的順序如下所述。
- 完全符合
- 提升轉換(如:char自動轉換為int,float自動轉換為double)
- 標準轉換(如:int轉換為char,long轉換為double)
- 使用者定義的轉換(如:類種定義的轉換建構函式)
進行完全符合時,C++允許一些無關緊要的轉換,如下表:
|實參|形參|
|---|---|
|Type | Type & |
|Type & | Type |
|Type[] | Type * |
|Type(argument-list) | Type()(argument-list) |
|Type | const Type |
|Type | volatile Type |
|Type | const Type * |
|Type * | volatile Type * |
1.2 函數覆蓋
這個概念都是描述基類和衍生類別之間函數關係的。函數覆蓋:是基類虛函數在衍生類別種被重新定義。如:
class Base{ virtual void show() { ... }}class BasePlus : pulic Base{ void show() { ... }}
這種形式是基類函數有virtual關鍵字,且衍生類別與基類函數名相同,參數列表也相同。如果參數列表不相同的話就是函數的隱藏了。而這種類型其實就是衍生類別函數把基類函數隱藏了。總之就是,在衍生類別種重新定義函數,將不是使用相同的函數特徵標覆蓋基類聲明,而是隱藏同名的基類方法,不管參數特徵標如何。所以,如果重新定義繼承的方法,應確保與原來的原型完全相同,但如果傳回型別是基類引用或指標,則可以修改為指向衍生類別的引用或指標。如果基類聲明被重載了,則應在衍生類別種重新定義所有的基類版本。因為如果只定義一個版本,則另外兩個版本將被隱藏,衍生類別對象將無法使用它們。如果不需要修改,則可以在衍生類別定義函數種只顯示調用基類方法即可。
1.3 函數隱藏
函數隱藏則分兩種情況,一種是基類有virtual關鍵字,但參數裡列表不同。另一種是在基類中無virtual關鍵字,但是衍生類別中有同名函數(參數列表相不相同都無所謂)。這時如果衍生類別對象調用該函數則執行衍生類別的同名函數,所有基類的同名函數都會被隱藏。此時衍生類別是不能調用基類的任何同名函數的。如下:
class Base{public: void show() { std::cout << "Base is running!" << std::endl; } void show(int n) { std::cout << "Base : " << n << std::endl; }};class BasePlus:public Base{public: void show() { std::cout << "BasePlus is running !" << std::endl; }};int main(){ BasePlus ob; ob.show(); // ob.show(1); // 錯誤 1 error C2660: “BasePlus::show”: 函數不接受 1 個參數 system("pause"); return 0;}
如果基類對象調用該函數則執行基類函數。如果是指向衍生類別對象的基類引用或指標則調用基類的函數。
1.4 小結
其實瞭解一下類的動態綁定就沒有這麼麻煩了,本來就是基類對象執行基類的函數,衍生類別對象執行衍生類別函數。如果基類有virtual關鍵字,則基類中則會有一個指向虛函數表的指標,此時如果衍生類別定義了同名函數,則虛函數標中的函數指標則定位到了衍生類別的函數。這就是函數覆蓋的現象。至於函數隱藏則更簡單了,沒有virtual關鍵字的情況下都是靜態聯編,自然是基類對象調用基類函數,衍生類別對象調用衍生類別函數了。如果有virtual關鍵字時,但是參數列表不一樣,自然會調用衍生類別的函數了。
2 建構函式、解構函式、拷貝建構函式、重載=在繼承的時候的一些問題2.1 建構函式和拷貝建構函式
建構函式不能是虛函數。建立衍生類別對象時,會先調用基類的建構函式,然後調用衍生類別的建構函式。如果基類沒有預設建構函式,則基類需要通過參數列表顯示地調用基類的建構函式,並傳遞參數給基類建構函式。
class Base_1{public: Base_1() { std::cout << "Base_1 defaut Constructor" << std::endl; }};class Base_2{public: Base_2(int n) { std::cout << "Base_2 self define Constructor:" << n << std::endl; }};class BasePlus_1 : public Base_1{public: BasePlus_1() { std::cout << "BasePlus_1 defaut Constructor" << std::endl; }};class BasePlus_2 : public Base_2{public: BasePlus_2():Base_2(2) // 預設建構函式 { std::cout << "BasePlus_2 defaut Constructor" << std::endl; }};class BasePlus_3 : public Base_1{public: BasePlus_3(int n) { std::cout << "BasePlus_3 self define Constructor:" << n << std::endl; }};class BasePlus_4 : public Base_2{public: BasePlus_4(int n):Base_2(n) { std::cout << "BasePlus_4 self define Constructor : " << n << std::endl; }};int main(){ BasePlus_1 ob_1; std::cout << "------------------------------------" << std::endl; BasePlus_2 ob_2; std::cout << "------------------------------------" << std::endl; BasePlus_3 ob_3(3); std::cout << "------------------------------------" << std::endl; BasePlus_4 ob_4(4); system("pause"); return 0;}
運行結果如下:
同樣的拷貝建構函式也是如此的。不可是虛擬,不可被繼承。調用機制和建構函式一樣。拷貝建構函式通常會在一下情況被調用:
Base ob_0;Base ob_1 = ob_0; // 此時會調用拷貝建構函式
Base ob_0;Base(ob_0); // 此時會調用拷貝建構函式,該對象為臨時對象
2.2 解構函式
解構函式應當是虛函數,除非類不用作基類。如果衍生類別對象銷毀時,會調用衍生類別解構函式然後調用基類解構函式,這樣運行時正確的。但如果基類的引用或指標指向衍生類別對象的時候,這時候該對象銷毀的時候會只調用基類的解構函式,此時如果衍生類別建構函式裡動態分配了記憶體的話,這部分記憶體就泄漏了。如果基類的解構函式是虛的,則會先調用衍生類別解構函式,然後再調用基類的解構函式。
class Base_3{public: ~Base_3() { std::cout << "Base_3 destructor" << std::endl; }};class Base_4{public: virtual ~Base_4() { std::cout << "Base_3 destructor" << std::endl; }};class BasePlus_5 : public Base_3{public: ~BasePlus_5() { std::cout << "BasePlus_5 destructor" << std::endl; }};class BasePlus_6 : public Base_4{public: ~BasePlus_6() { std::cout << "BasePlus_6 destructor" << std::endl; }};int main(){ Base_3 * pBase_3= new BasePlus_5; Base_4 * pBase_4 = new BasePlus_6; delete pBase_3; std::cout << "------------------------------------" << std::endl; delete pBase_4; system("pause"); return 0;}
運行結果如下:
通常應給基類提供一個虛解構函式,即使它並不需要解構函式
2.2 重載操作符=函數(賦值函數)
=操作符號與拷貝建構函式的調用情況是相同的,如果衍生類別沒有重載該操作符的時候。會調用基類的操作符函數(無論是重載的還是預設的),如果衍生類別定義了則會調用衍生類別重載的函數。所以為了保持基類部分資料的正確賦值,必須在衍生類別的重載函數中顯示調用基類的重載函數。如:
BasePlus & BasePlus::operator= (const BasePlus & ob){ Base::operator= (ob); // 顯示調用基類建構函式 ...}
這裡唯一不同的是,operator=(),函數可以是虛函數。那再什麼情況下我們需要operator=是虛函數呢?只有在兩個指向衍生類別對象的基類引用賦值的時候需要賦值衍生類別部分資料的時候我們希望通過多態調用衍生類別的賦值函數。但是很不幸目前這種操作實現不了,如下:
virtual Base & operator= (const Base & ob);BasePlus & operator= (const BasePlus & ob);
雖然基類賦值函數是虛函數,但是衍生類別函數並不是該函數的重新定義。也許是編譯器內部並沒有把virtual Base & operator= (const Base & ob)映射到虛函數表的原因,總之兩個指向衍生類別對象的基類引用賦值的時候調用的是基類的建構函式。如下:
// 基類class Base{public: virtual Base & operator= (const Base & ob) { std::cout << "BasePlus operator = is running" << std::endl; return * this; }};// 衍生類別class BasePlus : public Base{ BasePlus & operator= (const BasePlus & ob) { Base::operator= (ob);// 顯示調用基類函數 std::cout << "BasePlus operator = is running" << std::endl; return * this; }};int main(){ BasePlus objBasePlus_0; BasePlus objBasePlus_1; Base & obj_0 = objBasePlus_0; Base & obj_1 = objBasePlus_1; obj_0 = obj_1; system("pause"); return 0;}
輸出結果如下:
C++類中的一些細節(重載、重寫、覆蓋、隱藏,建構函式、解構函式、拷貝建構函式、賦值函數在繼承時的一些問題)