深入了解C++函數(shù)重載解析策略
參考《C++ Primer Plus》(第6版)中文版,Stephen Prata 著,張海龍 袁國忠譯,人民郵電出版社。C++ 使用重載解析策略來決定為函數(shù)調(diào)用使用哪一個函數(shù)定義。重載解析過程大致分為如下三步:
第 1 步:創(chuàng)建候選函數(shù)列表,只要求函數(shù)名一樣即可,對函數(shù)特征標以及是否為模板函數(shù)無要求;
第 2 步:在上一步的基礎(chǔ)上創(chuàng)建可行函數(shù)列表,包含特征標完全匹配的常規(guī)函數(shù)或模板函數(shù)、以及實參隱式轉(zhuǎn)換后完全匹配的常規(guī)函數(shù)或模板函數(shù),這些都是參數(shù)數(shù)目正確的函數(shù);
第 3 步:在上一步的基礎(chǔ)上確定最佳匹配函數(shù),若有則使用它,若沒有則該函數(shù)調(diào)用失敗。
下面以一個例子來說明這個重載過程:
//全部函數(shù)原型 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 //函數(shù)調(diào)用 may('B'); //函數(shù)定義 ...
重載第 1 步:創(chuàng)建候選函數(shù)列表。即函數(shù)名稱為 may 的常規(guī)函數(shù)和模板函數(shù),候選函數(shù)列表如下:
//重載第1步:創(chuàng)建候選函數(shù)列表 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 步:創(chuàng)建可行函數(shù)列表。由于整數(shù)類型 char 不能被隱式地轉(zhuǎn)換為指針類型 char *,因此函數(shù) #4 和函數(shù) #7 都被排除,而函數(shù) #8 因為參數(shù)數(shù)目不匹配也會被排除。進行完全匹配時,C++ 允許下表這些無關(guān)緊要的轉(zhuǎn)換,表中 Type 表示任意類型,例如 char & 到 const char & 的轉(zhuǎn)換也包含在內(nèi),表中 Type (argument-list) 意味著用作實參的函數(shù)名和用作形參的函數(shù)指針只要返回類型和參數(shù)列表相同,就是匹配的。
實參類型 | 形參類型 |
---|---|
Type | Type & |
Type & | Type |
Type [] | Type * |
Type (argument-list) | Type (*) (argument-list) |
Type | const Type |
Type | volatile Type |
Type * | const Type * |
Type * | volatile Type * |
根據(jù)此表可知,剩下的函數(shù)中包含特征標完全匹配的常規(guī)函數(shù) #3 和 #5、特征標完全匹配的模板函數(shù) #6(此時 T 可以被實例化為 char)、實參隱式轉(zhuǎn)換后完全匹配的常規(guī)函數(shù) #1 和 #2??尚泻瘮?shù)列表如下:
//重載第2步:創(chuàng)建可行函數(shù)列表 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 步:確定最佳匹配函數(shù)。通常,從最佳到最差的順序如下所述:
- 特征標完全匹配;
- 類型需經(jīng)隱式提升轉(zhuǎn)換,例如 char 和 short 自動轉(zhuǎn)換為 int,float 自動轉(zhuǎn)換為 double;
- 類型需經(jīng)隱式標準轉(zhuǎn)換,例如 int 轉(zhuǎn)換為 char,long 轉(zhuǎn)換為 double;
- 類型需經(jīng)隱式自定義轉(zhuǎn)換,例如類中用戶定義的類型轉(zhuǎn)換。
依此規(guī)則,函數(shù) #3 和函數(shù) #5、函數(shù) #6 都是特征標完全匹配的最佳匹配函數(shù),函數(shù) #1 需經(jīng)隱式提升轉(zhuǎn)換,函數(shù) #2 需經(jīng)隱式標準轉(zhuǎn)換,由此各函數(shù)最佳匹配程度為:(#3, #5, #6) > #1 > #2。當特征標完全匹配時,又有如下規(guī)則:
- 指向非 const 數(shù)據(jù)的指針和引用優(yōu)先與形參為非 const 指針和引用的函數(shù)匹配;
- 優(yōu)先與非模板函數(shù)匹配;
- 同為模板函數(shù)時,優(yōu)先與較具體的模板函數(shù)匹配。
依此規(guī)則,非模板函數(shù) #3 和 #5 最佳匹配程度要高于模板函數(shù) #6 ,即各函數(shù)最佳匹配程度為:(#3, #5) > #6 > #1 > #2。最終出現(xiàn)了兩個最佳匹配函數(shù) #3 和 #5 ,因此該函數(shù)調(diào)用失敗,編譯器將報錯。
//重載第 3 步:確定最佳匹配函數(shù) void may(char); //原型#3 char may(const char &); //原型#5
下面展開來說上述幾條完全匹配時的規(guī)則。
第 1 條:指向非 const 數(shù)據(jù)的指針和引用優(yōu)先與形參為非 const 指針和引用的函數(shù)匹配,這一點需明確,const 和非 const 之間的區(qū)別只適用于指針和引用。下面 4 個函數(shù)都與函數(shù)調(diào)用是完全匹配的:
//函數(shù)原型 void recycle(int); //原型#1 void recycle(const int); //原型#2 void recycle(int &); //原型#3 void recycle(const int &);//原型#4 //函數(shù)調(diào)用 int x = 5; recycle(x); //函數(shù)定義 ...
- 如果這 4 個函數(shù)同時存在,則無法完成重載,編譯器會報多義性匹配的錯誤;
- 如果只存在函數(shù) #1 與 #2,則無法完成重載,編譯器會報重復(fù)定義的錯誤;
- 如果只存在函數(shù) #1 與 #3,則無法完成重載,編譯器會報多義性匹配的錯誤;
- 如果只存在函數(shù) #1 與 #4,則無法完成重載,編譯器會報多義性匹配的錯誤;
- 如果只存在函數(shù) #2 與 #3,則無法完成重載,編譯器會報多義性匹配的錯誤;
- 如果只存在函數(shù) #2 與 #4,則無法完成重載,編譯器會報多義性匹配的錯誤;
- 如果只存在函數(shù) #3 與 #4,則函數(shù)調(diào)用時編譯器將會選擇 #3。
第 2 條:優(yōu)先與非模板函數(shù)匹配,這一點比較簡單,當完全匹配的函數(shù)中,一個是非模板函數(shù),另一個是模板函數(shù)時,非模板函數(shù)將優(yōu)于模板函數(shù),顯式具體化、顯式實例化、隱式實例化都屬于模板函數(shù)。
第 3 條:同為模板函數(shù)時,優(yōu)先與較具體的模板函數(shù)匹配,找出最具體的模板的規(guī)則被稱為函數(shù)模板的部分排序規(guī)則(partial ordering rules)。這意味著顯式具體化優(yōu)先于常規(guī)模板函數(shù),都為常規(guī)模板函數(shù)時,編譯器優(yōu)先選擇實例化時類型轉(zhuǎn)換更少的那一個。以下面的程序為例,調(diào)用方式 recycle(&ink) 既與模板 #1 匹配,此時 Type 將被解釋為 blot *,也與模板 #2 匹配,此時 Type 將被解釋為 blot,因此將這兩個隱式實例 recycle<blot *>(blot *) 和 recycle<blot>(blot *) 發(fā)送到可行函數(shù)池中。在選擇最佳匹配函數(shù)時,#2 被認為是更具體的,因為它已經(jīng)顯式地指出,函數(shù)參數(shù)是指向 Type 的指針,相比于 #1,它對類型的要求更加地具體,在生成過程中所需要的轉(zhuǎn)換更少,因此調(diào)用方式 recycle(&ink) 實際會匹配版本 #2。
//兩個常規(guī)模板函數(shù) template <class Type> void recycle(Type t); //原型#1 template <class Type> void recycle(Type * t); //原型#2 //調(diào)用程序包含如下代碼 struct blot {int a; char b[10];}; blot ink = {25, "spots"}; ... recycle(&ink); //使用版本#2 //函數(shù)定義 ...
部分排序規(guī)則的另一個示例程序如下,它與上一個例子有異曲同工之妙。由于模板 #2 做了特定的假設(shè):數(shù)組內(nèi)容是指針,對類型的要求更加地具體,因此在調(diào)用時第一個參數(shù)若傳入指針數(shù)組 pt,則將實際匹配函數(shù) #2。
//兩個常規(guī)模板函數(shù) template <typename T> void ShowArray(T arr[], int n); //原型#1 template <typename T> void ShowArray(T * arr[], int n); //原型#2 //調(diào)用程序包含如下代碼 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 //函數(shù)定義 ...
將有多個參數(shù)的函數(shù)調(diào)用與有多個參數(shù)的原型進行匹配時,編譯器必須考慮所有參數(shù)的匹配情況。如果找到比其他可行函數(shù)都合適的函數(shù),則選擇該函數(shù)。一個函數(shù)要比其他函數(shù)都合適,其所有參數(shù)的匹配程度都必須不比其他函數(shù)差,同時至少有一個參數(shù)的匹配程度比其他函數(shù)都高。
在有些情況下,可通過編寫合適的函數(shù)調(diào)用,來引導(dǎo)編譯器做出程序員期望的選擇。如下所示,其中模板函數(shù)返回兩個值中較小的一個,非模板函數(shù)返回兩個值中絕對值較小的那個。第一次調(diào)用時根據(jù)重載解析策略選擇了非模板函數(shù) #2;第二次調(diào)用時根據(jù)重載解析策略選擇了模板函數(shù) #1 的 double 版本,屬于模板函數(shù)的隱式實例化;第三次調(diào)用的 <> 指出,編譯器應(yīng)該選擇模板函數(shù),此時編譯器會查看調(diào)用函數(shù)時的實參類型來進行實例化,也屬于模板函數(shù)的隱式實例化;第四次調(diào)用的 <int> 顯式指出,編譯器應(yīng)該使用模板函數(shù)的 int 實例化版本,此時屬于模板函數(shù)的顯式實例化。
#include <iostream> //函數(shù)#1 template<class T> T lesser(T a, T b) { return a < b ? a : b; } //函數(shù)#2 int lesser(int a, int b) { a = a < 0 ? -a : a; b = b < 0 ? -b : b; return a < b ? a : b; } //函數(shù)調(diào)用 int main() { using namespace std; int m = 20; int n = -30; double x = 15.5; double y = 25.9; //使用#2,結(jié)果為20 cout << lesser(m, n) << endl; //使用#1,double隱式實例化,結(jié)果為15.5 cout << lesser(x, y) << endl; //使用#1,int隱式實例化,結(jié)果為-30 cout << lesser<>(m, n) << endl; //使用#1,int顯式實例化,結(jié)果為15 cout << lesser<int>(x, y) << endl; return 0; }
到此這篇關(guān)于深入了解C++函數(shù)重載解析策略的文章就介紹到這了,更多相關(guān)C++函數(shù)重載解析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++學(xué)習(xí)小結(jié)之數(shù)據(jù)類型及轉(zhuǎn)換方式
本文給大家分享的是本人在學(xué)習(xí)C++過程中的一個小心得,關(guān)于數(shù)據(jù)類型和轉(zhuǎn)換方式的,這里記錄下來,推薦給菜鳥們,高手大神請直接飄過。2015-07-07一起來學(xué)習(xí)C語言的程序環(huán)境與預(yù)處理
這篇文章主要為大家詳細介紹了C語言程序環(huán)境與預(yù)處理,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03C語言實現(xiàn)簡單的學(xué)生學(xué)籍管理系統(tǒng)
這篇文章主要為大家詳細介紹了C語言實現(xiàn)簡單的學(xué)生學(xué)籍管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-07-07C語言 數(shù)據(jù)結(jié)構(gòu)鏈表的實例(十九種操作)
這篇文章主要介紹了C語言 數(shù)據(jù)結(jié)構(gòu)鏈表的實例(十九種操作)的相關(guān)資料,需要的朋友可以參考下2017-07-07