C不支持函數重載,C++支持程序員
代碼演示ide
main.c函數
1 #include<stdio.h> 2 3 void Max(int a, int b) 4 { 5 printf("%d ", a > b ? a : b) ; 6 } 7 8 void Max(double a, double b) 9 { 10 printf("%f ", a > b ? a : b); 11 } 12 13 int main() 14 { 15 Max(1,2); 16 Max(1.0, 2.0); 17 return 0; 18 }
因爲C不支持重載,上述代碼編譯時報錯 錯誤 C2084 函數「void Max(int,int)」已有主體。spa
main.cpp指針
1 #include<stdio.h> 2 3 void Max(int a, int b) 4 { 5 printf("%d ", a > b ? a : b) ; 6 } 7 8 void Max(double a, double b) 9 { 10 printf("%f ", a > b ? a : b); 11 } 12 13 int main() 14 { 15 Max(1,2); 16 Max(1.0, 2.0); 17 return 0; 18 }
正常編譯執行。code
代碼是同樣的,僅僅是改個文件後綴,至關於從C編譯器換成C++編譯器,差別竟如此之大。代碼裏面咱們定義2個Max函數,Max這個函數名字是給咱們程序員看的,並非編譯器看的。編譯器看代函數的視角決定了是否支持重,編譯器也是以這種視角來區分不一樣函數的。下面介紹編譯器識別函數名字規則。
blog
C/C++名字修飾約定字符串
如下截圖在VC6.0下生成原型
C編譯器名字修飾約定編譯器
C的名字修飾約定比較簡單粗暴,他不考慮返回值以及參數,只看函數名。而後,無論是啥函數名,在編譯器眼中統一是_函數名。因此main.c那個代碼編譯報錯緣由就很顯而易見了,編譯器發現兩個_Max函數,因而報錯 XXX函數已有主體。
C++編譯器名字修飾約定
C++名字約定不只要考慮函數名字,還要考慮參數,返回值。具體見名字約定
名字約定
修飾名(Decoration name)
「C」或者「C++」函數在內部(編譯和連接)經過修飾名識別。
修飾名是編譯器在編譯函數定義或者原型時生成的字符串。
有些狀況下使用函數的修飾名是必要的,如在模塊定義文件裏頭指定輸出「C++」重載函數、構造函數、析構函數,又如在彙編代碼裏調用「C」」或「C++」函數等。
修飾名由函數名、類名、調用約定、返回類型、參數等共同決定。
名字修飾約定隨調用約定和編譯種類(C或C++)的不一樣而變化
修飾名隨編譯種類和調用約定的不一樣而不一樣,下面分別說明。
C編譯時函數名修飾約定規則:
__stdcall
約定在輸出函數名前加上一個下劃線前綴,後面加上一個「@」符號和其參數的字節數,格式爲_functionname@number。
__cdecl
約定僅在輸出函數名前加上一個下劃線前綴,格式爲_functionname。
__fastcall
約定在輸出函數名前加上一個「@」符號,後面也是一個「@」符號和其參數的字節數,格式爲@functionname@number。
它們均不改變輸出函數名中的字符大小寫,這和PASCAL調用約定不一樣,PASCAL約定輸出的函數名無任何修飾且所有大寫。
C++編譯時函數名修飾約定規則:
__stdcall
①以「?」標識函數名的開始,後跟函數名
②函數名後面以「@@YG」標識參數表的開始,後跟參數表
③參數表以代號表示
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」表明一次重複;
參數表的第一項爲該函數的返回值類型
其後依次爲參數的數據類型,指針標識在其所指數據類型前
參數表後以「@Z」標識整個名字的結束
若是該函數無參數,則以「Z」標識結束。
其格式爲「?functionname@@YA*****@Z」或「?functionname@@YG*XZ」,例如
__cdecl
規則同上面的_stdcall調用約定,只是參數表的開始標識爲「@@YA」。
__fastcall
規則同上面的_stdcall調用約定,只是參數表的開始標識爲「@@YI」。
VC++對函數的省缺聲明是"__cedcl",將只能被C/C++調用.
CB在輸出函數聲明時使用4種修飾符號
//__cdecl
cb的默認值,它會在輸出函數名前加_,並保留此函數名不變,參數按照從右到左的順序依次傳遞給棧,也能夠寫成_cdecl和cdecl形式。
//__fastcall
她修飾的函數的參數將盡肯呢感地使用寄存器來處理,其函數名前加@,參數按照從左到右的順序壓棧;
//__pascal
它說明的函數名使用Pascal格式的命名約定。這時函數名所有大寫。參數按照從左到右的順序壓棧;
//__stdcall
使用標準約定的函數名。函數名不會改變。使用__stdcall修飾時。參數按照由右到左的順序壓棧,也能夠是_stdcall;
extern 「C」
在C++代碼中常常看到在函數以前加上extern "C",代碼以下
1 #include<stdio.h> 2 3 extern "C" void Max(int a, int b) 4 { 5 printf("%d ", a > b ? a : b); 6 } 7 8 void Max(double a, double b) 9 { 10 printf("%f ", a > b ? a : b); 11 } 12 13 int main() 14 { 15 Max(1, 2); 16 Max(1.0, 2.0); 17 return 0; 18 }
這種作法的本質是讓C++編譯器不採用C++方式編譯函數,而是採用C的方式編譯。這樣致使編譯器看待函數名稱是不一樣的
上面代碼是能夠編譯過的,若是給另外一個Max函數也使用extern "C"則沒法編譯經過,道理也很顯然,此時代碼按照C變異角度看有2個_Max函數,所以編譯不過。
1 #include<stdio.h> 2 3 extern "C" void Max(int a, int b) 4 { 5 printf("%d ", a > b ? a : b); 6 } 7 8 extern "C" void Max(double a, double b) 9 { 10 printf("%f ", a > b ? a : b); 11 } 12 13 int main() 14 { 15 Max(1, 2); 16 Max(1.0, 2.0); 17 return 0; 18 }
重載爲何不能只靠返回值的不一樣而進行重載
函數只有返回值不一樣不能夠構成重載,代碼以下,假設以__cdecl方式調用
#include<stdio.h> //?Max@@YAHHH@Z void Max(int a, int b) { printf("%d ", a > b ? a : b); } //?Max@@YANHH@Z void Max(int a, int b) { printf("%f ", a > b ? a : b); } int main() { Max(1, 2); Max(1.0, 2.0); return 0; }
咱們在調用函數時並無寫函數返回值,形如(沒有這麼寫的,這麼寫是錯的)
int Max(1, 2);
double Max(1.0, 2.0);
這樣就能夠依據返回值區分不一樣函數調用。但現實狀況是函數調用依據函數名和參數,調用時是不清楚返回值的。若是函數名,參數都同樣,編譯器就懵逼了