函數的重載詳解
什么時函數重載:
函數重載是指在同一作用域內,可以有一組具有相同函數名,不同參數列表的函數,這組函數被稱為重載函數。重載函數通常用來命名一組功能相似的函數,這樣做減少了函數名的數量,避免了名字空間的污染,對於程序的可讀性有很大的好處。
1.是函數的一種特殊情況,C++允許在同一作用域中聲明幾個功能類似的同名函數,這些同名函數的形參列表(參數“個數” 或 “類型” 或 “順序”)必須不同,常用來處理實現功能類似數據類型不同的問題(這也是C++與C語言的最重要區別)
1 int Add(int left, int right)
2 { 3 return left+right; 4 } 5 6 double Add(double left, double right) 7 { 8 return left+right; 9 } 10 11 long Add(long left, long right) 12 { 13 return left+right; 14 } 15 16 int main() 17 { 18 Add(10, 20); 19 Add(10.0, 20.0); 20 Add(10L, 20L); 21 22 return 0; 23 }
例如這里定義了三個Add函數,傳入的參數與順序都相同,都是 left/ right ,但參數的類型卻不同,有 int型,long型, double型,所以可以依靠這些類型的不同來區分要調運哪個函數。
在這里要特別注意:這里的參數必須是參數的 個數 或 類型 或 順序不同,如果是返回值不同,則不屬於函數重載,如:Add前的int, long, double。
下面就介紹一下在編譯時具體的名字修飾規則:
2.名字修飾
Name Mangling是一種在編譯過程中,將函數、變量的名稱重新改編的機制,簡單來說就是編譯器為了區分各 個函數,將函數通過某種算法,重新修飾為一個全局唯一的名稱。
C語言的名字修飾規則非常簡單,只是在函數名字前面添加了下划線。比如,對於以下代碼,在后鏈接時就 會出錯:
1 int Add(int left, int right);
2
3 int main() 4 { 5 Add(1, 2); 6 return 0; 7 }
編譯器報錯:error LNK2019: 無法解析的外部符號 _Add,該符號在函數 _main 中被引用。
上述Add函數只給了聲明沒有給定義,因此在鏈接時就會報錯,提示:在main函數中引用的Add函數找不到函數體。從報錯結果中可以看到,C語言只是簡單的在函數名前添加下划線。因此當工程中存在相同函數名的函數時,就會產生沖突。
由於C++要支持函數重載,命名空間等,使得其修飾規則比較復雜,不同編譯器在底層的實現方式可能都有差異
1 int Add(int left, int right);
2 double Add(double left, double right); 3 4 int main() 5 { 6 Add(1, 2); 7 Add(1.0, 2.0); 8 return 0; 9 }
在vs下,對上述代碼進行編譯鏈接,后編譯器報錯:
error LNK2019: 無法解析的外部符號 "double cdecl Add(double,double)" (?Add@@YANNN@Z) error LNK2019: 無法解析的外部符號 "int __cdecl Add(int,int)" (?Add@@YAHHH@Z)
通過上述錯誤可以看出,編譯器實際在底層使用的不是Add名字,而是被重新修飾過的一個比較復雜的名字, 被重新修飾后的名字中包含了:函數的名字以及參數類型。這就是為什么函數重載中幾個同名函數要求其參數列表不同的原因。只要參數列表不同,編譯器在編譯時通過對函數名字進行重新修飾,將參數類型包含在終的名字中,就可保證名字在底層的全局唯一性。
比較c/c++中的名字修飾:
a、C編譯時函數名修飾約定規則:
__stdcall調用約定在輸出函數名前加上一個下划線前綴,后面加上一個“@”符號和其參數的字節數,
格式為_functionname@number。
__cdecl調用約定僅在輸出函數名前加上一個下划線前綴,格式為_functionname。
__fastcall調用約定在輸出函數名前加上一個“@”符號,后面也是一個“@”符號和其參數的字節數,
格式為@functionname@number。
它們均不改變輸出函數名中的字符大小寫,這和PASCAL調用約定不同,PASCAL約定輸出的函數名無任何修飾且全部大寫。
b、C++編譯時函數名修飾約定規則:
__stdcall調用約定:
1、以“?”標識函數名的開始,后跟函數名;
2、函數名后面以“@@YG”標識參數表的開始,后跟參數表;
3、參數表以代號表示:
X--void ,
D--char,
E--unsigned char,
F--short,
H--int,
I--unsigned int,
J--long,
K--unsigned long,
M--float,
N--double,
_N--bool,
....
PA--表示指針,后面的代號表明指針類型,如果相同類型的指針連續出現,以“0”代替,一個“0”代 表一次重復;
4、參數表的第一項為該函數的返回值類型,其后依次為參數的數據類型,指針標識在其所指數據類型前;
5、參數表后以“@Z”標識整個名字的結束,如果該函數無參數,則以“Z”標識結束。
其格式為“?functionname@@YG*****@Z”或“?functionname@@YG*XZ”,例如
int Test1(char *var1,unsigned long)-----“?Test1@@YGHPADK@Z”
void Test2() -----“?Test2@@YGXXZ”
__cdecl調用約定:
規則同上面的_stdcall調用約定,只是參數表的開始標識由上面的“@@YG”變為“@@YA”。
__fastcall調用約定:
規則同上面的_stdcall調用約定,只是參數表的開始標識由上面的“@@YG”變為“@@YI”。
VC++對函數的省缺聲明是"__cedcl",將只能被C/C++調用