參考《C++ Primer Plus》(第6版)中文版,Stephen Prata 著,張海龍 袁國忠譯,人民郵電出版社。C++ 使用過載解析策略來決定為函數呼叫使用哪一個函數定義。過載解析過程大致分為如下三步:
下面以一個例子來說明這個過載過程:
//全部函數原型
void may(int); //原型#1
float may(float, float = 3); //原型#2
void may(char); //原型#3
char * may(const char *); //原型#4
char may(const char &); //原型#5
template<class T> void may(const T &);//原型#6
template<class T> void may(T *); //原型#7
void may(char, double); //原型#8
void mbk(float); //原型#9
char mkk(int, char); //原型#10
int mck(char); //原型#11
double myk(float); //原型#12
void mpk(char); //原型#13
//函數呼叫
may('B');
//函數定義
...
過載第 1 步:建立候選函數列表。即函數名稱為 may
的常規函數和模板函數,候選函數列表如下:
//過載第1步:建立候選函數列表
void may(int); //原型#1
float may(float, float = 3); //原型#2
void may(char); //原型#3
char * may(const char *); //原型#4
char may(const char &); //原型#5
template<class T> void may(const T &);//原型#6
template<class T> void may(T *); //原型#7
void may(char, double); //原型#8
過載第 2 步:建立可行函數列表。由於整數型別 char
不能被隱式地轉換為指標型別 char *
,因此函數 #4
和函數 #7
都被排除,而函數 #8
因為引數數目不匹配也會被排除。進行完全匹配時,C++ 允許下表這些無關緊要的轉換,表中 Type
表示任意型別,例如 char &
到 const char &
的轉換也包含在內,表中 Type (argument-list)
意味著用作實參的函數名和用作形參的函數指標只要返回型別和參數列相同,就是匹配的。
實參型別 | 形參型別 |
---|---|
Type |
Type & |
Type & |
Type |
Type [] |
Type * |
Type (argument-list) |
Type (*) (argument-list) |
Type |
const Type |
Type |
volatile Type |
Type * |
const Type * |
Type * |
volatile Type * |
根據此表可知,剩下的函數中包含特徵標完全匹配的常規函數 #3
和 #5
、特徵標完全匹配的模板函數 #6
(此時 T
可以被範例化為 char
)、實參隱式轉換後完全匹配的常規函數 #1
和 #2
。可行函數列表如下:
//過載第2步:建立可行函數列表
void may(int); //原型#1
float may(float, float = 3); //原型#2
void may(char); //原型#3
char may(const char &); //原型#5
template<class T> void may(const T &);//原型#6
過載第 3 步:確定最佳匹配函數。通常,從最佳到最差的順序如下所述:
char
和 short
自動轉換為 int
,float
自動轉換為 double
;int
轉換為 char
,long
轉換為 double
;依此規則,函數 #3
和函數 #5
、函數 #6
都是特徵標完全匹配的最佳匹配函數,函數 #1
需經隱式提升轉換,函數 #2
需經隱式標準轉換,由此各函數最佳匹配程度為:(#3, #5, #6) > #1 > #2
。當特徵標完全匹配時,又有如下規則:
const
資料的指標和參照優先與形參為非 const
指標和參照的函數匹配;依此規則,非模板函數 #3
和 #5
最佳匹配程度要高於模板函數 #6
,即各函數最佳匹配程度為:(#3, #5) > #6 > #1 > #2
。最終出現了兩個最佳匹配函數 #3
和 #5
,因此該函數呼叫失敗,編譯器將報錯。
//過載第 3 步:確定最佳匹配函數
void may(char); //原型#3
char may(const char &); //原型#5
下面展開來說上述幾條完全匹配時的規則。
第 1 條:指向非 const
資料的指標和參照優先與形參為非 const
指標和參照的函數匹配,這一點需明確,const
和非 const
之間的區別只適用於指標和參照。下面 4 個函數都與函數呼叫是完全匹配的:
//函數原型
void recycle(int); //原型#1
void recycle(const int); //原型#2
void recycle(int &); //原型#3
void recycle(const int &);//原型#4
//函數呼叫
int x = 5;
recycle(x);
//函數定義
...
#1
與 #2
,則無法完成過載,編譯器會報重複定義的錯誤;#1
與 #3
,則無法完成過載,編譯器會報多義性匹配的錯誤;#1
與 #4
,則無法完成過載,編譯器會報多義性匹配的錯誤;#2
與 #3
,則無法完成過載,編譯器會報多義性匹配的錯誤;#2
與 #4
,則無法完成過載,編譯器會報多義性匹配的錯誤;#3
與 #4
,則函數呼叫時編譯器將會選擇 #3
。第 2 條:優先與非模板函數匹配,這一點比較簡單,當完全匹配的函數中,一個是非模板函數,另一個是模板函數時,非模板函數將優於模板函數,顯式具體化、顯式範例化、隱式範例化都屬於模板函數。
第 3 條:同為模板函數時,優先與較具體的模板函數匹配,找出最具體的模板的規則被稱為函數模板的部分排序規則(partial ordering rules)。這意味著顯式具體化優先於常規模板函數,都為常規模板函數時,編譯器優先選擇範例化時型別轉換更少的那一個。以下面的程式為例,呼叫方式 recycle(&ink)
既與模板 #1
匹配,此時 Type
將被解釋為 blot *
,也與模板 #2
匹配,此時 Type
將被解釋為 blot
,因此將這兩個隱式範例 recycle<blot *>(blot *)
和 recycle<blot>(blot *)
傳送到可行函數池中。在選擇最佳匹配函數時,#2
被認為是更具體的,因為它已經顯式地指出,函數引數是指向 Type
的指標,相比於 #1
,它對型別的要求更加地具體,在生成過程中所需要的轉換更少,因此呼叫方式 recycle(&ink)
實際會匹配版本 #2
。
//兩個常規模板函數
template <class Type> void recycle(Type t); //原型#1
template <class Type> void recycle(Type * t); //原型#2
//呼叫程式包含如下程式碼
struct blot {int a; char b[10];};
blot ink = {25, "spots"};
...
recycle(&ink); //使用版本#2
//函數定義
...
部分排序規則的另一個範例程式如下,它與上一個例子有異曲同工之妙。由於模板 #2
做了特定的假設:陣列內容是指標,對型別的要求更加地具體,因此在呼叫時第一個引數若傳入指標陣列 pt
,則將實際匹配函數 #2
。
//兩個常規模板函數
template <typename T>
void ShowArray(T arr[], int n); //原型#1
template <typename T>
void ShowArray(T * arr[], int n); //原型#2
//呼叫程式包含如下程式碼
int things[6] = {13, 31, 103, 301, 310, 130};
int * pt[3] = {&things[0], &things[2], &things[4]};
ShowArray(things, 6); //使用版本#1
ShowArray(pt, 3); //使用版本#2
//函數定義
...
將有多個引數的函數呼叫與有多個引數的原型進行匹配時,編譯器必須考慮所有引數的匹配情況。如果找到比其他可行函數都合適的函數,則選擇該函數。一個函數要比其他函數都合適,其所有引數的匹配程度都必須不比其他函數差,同時至少有一個引數的匹配程度比其他函數都高。
在有些情況下,可通過編寫合適的函數呼叫,來引導編譯器做出程式設計師期望的選擇。如下所示,其中模板函數返回兩個值中較小的一個,非模板函數返回兩個值中絕對值較小的那個。第一次呼叫時根據過載解析策略選擇了非模板函數 #2
;第二次呼叫時根據過載解析策略選擇了模板函數 #1
的 double
版本,屬於模板函數的隱式範例化;第三次呼叫的 <>
指出,編譯器應該選擇模板函數,此時編譯器會檢視呼叫函數時的實參型別來進行範例化,也屬於模板函數的隱式範例化;第四次呼叫的 <int>
顯式指出,編譯器應該使用模板函數的 int
範例化版本,此時屬於模板函數的顯式範例化。
#include <iostream>
//函數#1
template<class T>
T lesser(T a, T b)
{
return a < b ? a : b;
}
//函數#2
int lesser(int a, int b)
{
a = a < 0 ? -a : a;
b = b < 0 ? -b : b;
return a < b ? a : b;
}
//函數呼叫
int main()
{
using namespace std;
int m = 20;
int n = -30;
double x = 15.5;
double y = 25.9;
//使用#2,結果為20
cout << lesser(m, n) << endl;
//使用#1,double隱式範例化,結果為15.5
cout << lesser(x, y) << endl;
//使用#1,int隱式範例化,結果為-30
cout << lesser<>(m, n) << endl;
//使用#1,int顯式範例化,結果為15
cout << lesser<int>(x, y) << endl;
return 0;
}
本文作者:木三百川
本文連結:https://www.cnblogs.com/young520/p/16808445.html
版權宣告:本文系博主原創文章,著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請附上出處連結。遵循 署名-非商業性使用-相同方式共用 4.0 國際版 (CC BY-NC-SA 4.0) 版權協定。