C++支援函式過載,而C語言卻不支援,原來是這個原因!
為什麼C++支援函式過載而C語言卻不支援?
1.函式過載的概念
如果你接觸過C++,那麼一定使用過插入運算子"<<“和提取運算子”>>"吧。這倆個運算子是C和C++位運算子中的左移運算子和右移運算子,而C++又把它作為輸入輸出運算子。允許一個運算子可以用於不同場合,不同的場合就有不同的含義,這就叫做運算子的 “過載”,即重新賦予它新的含義。這其實就是 “一物多用”。
在C++中,函式也可以過載。C++允許在同一作用域中用同一函式名定義多個函式,這些函式的引數列表不同,這些同名的函式用來實現不同的功能。這就是函式的過載,即一個函式名多用。
例項:分別求倆個int,double及long型別的資料之和。
#include <iostream>
using namespace std;
int Add(int x, int y)
{
return x + y;
}
double Add(double x, double y)
{
return x + y;
}
long Add(long x, long y)
{
return x + y;
}
int main()
{
int ret1 = Add(1, 2);
double ret2 = Add(1.23, 2.34);
long ret3 = Add(10L, 20L);
cout << "int: " << ret1 << endl;
cout << "double: " << ret2 << endl;
cout << "long: " << ret3 << endl;
return 0;
}
main函式三次呼叫Add函式,但每次實參的型別不同。系統會根據實參的型別找到與之匹配的函式,然後呼叫該函式。
注意: 上述情況只是函式引數列表不同的其中一種表現,函式引數列表不同,總共包括如下三種種情況
- 引數的個數不同
- 引數的型別相同
- 引數的次序不同
函式的函式名相同,函式的引數列表滿足以上三種情況中的一種或多種,都可以構成函式的過載。
2.函式名修飾----造成差異的真正原因
函式的編譯過程+連結
要深入理解這塊的內容,我們還得從程式的編譯過程說起,C/C++中,一個程式要執行起來,必須經歷如下幾個階段:預處理、編譯、彙編、連結。
我在C語言學習階段寫過一篇部落格來總結程式的編譯過程,其中畫過一張圖拿到這裡也同樣適用(並附上我之前那篇部落格的連結,有興趣的讀者可以瀏覽瀏覽-----深入理解程式的編譯過程+連結)
有必要把那篇部落格中的部分內容也放到這裡做一個參考。
而在這裡,我們著重需要研究的是連結這個部分。我們實際的專案通常是由多個原始檔構成的,這裡假設在main.c/cpp中呼叫了在add.c/cpp中定義的Add函式,我們根據之前的知識可以知道,在編譯生成.o檔案之後,連結之前,main.o中是沒有Add的函式地址的,因為Add是在add.c/cpp中定義的,所以Add的地址是在add.o中。那怎麼處理呢?
連結階段就是專門用來處理這種問題,連結器看到main.o呼叫Add,但是沒有Add的地址,就會到add.o的符號表中找Add的地址,然後連結到一起。 那麼重點就來了,連結時,面對Add函式,連結器會根據什麼樣的名字去尋找它呢,在這裡,不同的編譯器會有不同的函式名修飾規則。
Linux下的函式名修飾規則
由於Linux下gcc/g++的修飾規則簡單易懂,下面我們以gcc/g++為例來觀察這個函式修飾後的名字。
- 使用gcc編譯test.c
可以看出,在Liunx下采用gcc編譯後,函式的名字的修飾沒有發生改變, 和原函式名無區別。
- 使用g++編譯test.cpp
而採用g++編譯完成後,函式名字的修飾發生改變,編譯器將函式引數型別資訊新增到修改後的名字中。
其實上面的例子對比下來以足以說明問題,但為了進一步感受函式過載,我們再觀察一下下邊的這個案例,從而更加確定我們的結論。
結論: 通過上述舉例,我們可以看出gcc的函式修飾後名字不變。而g++的函式修飾後變成[_Z+函式長度+函式名+型別首字母]。
看到這裡,大家可能就會豁然開朗,正是因為C語言中的函式名無法區分,而C++的函式修飾中增加了函式型別的緣故,從而儘管是函式名相同但只要引數列表不同,編譯器也會認為是不同的函式, 這樣就支援了過載。
Windows下VS中的函式名修飾
怎樣檢視VS中編譯器對函式名是怎樣修飾的呢?這裡我提供一個有趣的做法:
還是以Add函式為例,我們新建三個原始檔,分別是main.c(呼叫Add函式)、add.h(放置Add函式宣告)、add.c(Add函式實現)。
做好上述工作之後,如果各環節程式碼均完整且無誤,則可順利通過編譯及連結,但這卻並不是我們的目的。我們需要做的是,將add.c中的函式實現刪掉(或註釋掉即可),再次編譯並連結,就會得到報錯資訊如下(需要注意如果是僅編譯並不會報錯,因為程式碼語法本身不存在問題):
回憶文章前邊描述連結過程可知,由於程式在連結時找不到Add函式的實現,則出現上述報錯,而我們需要關注的是函式名由Add改成了_Add, 僅僅是前邊多了一個下劃線,這也正是VS2013的編譯器對C語言中函式名字的修飾,同樣沒有和型別相關的任何內容。
這時,原始碼完全不進行更改,只需將所有的.c檔案換成.cpp檔案,再次進行編譯連結,這時則會看到如下的報錯資訊:
同樣型別的錯誤提示,但可以很明顯的看到,對於同樣的Add函式,函式名由Add改成了 ?Add@@YAHHH@Z。雖然我們好像看不太懂具體是什麼意思,但有了以上的經驗,我們也能大致猜測是C++的編譯器對函式名字的修飾中加入了型別等元素導致的,因為C++需要支援函式的過載。
至於這種複雜的命名修飾到底分別代表什麼含義,這裡就不在繼續解釋,原因之一是博主到現在都還沒完成整明白呢!有興趣的讀者自己可以深入研究一下。
3.extern “C”
通過上述的結論可知,C語言和C++對於函式的修飾規則不同,這也是倆種語言編譯風格不同的一種體現。
有時候在C++工程中可能需要將某些函式按照C的風格來編譯, 在函式前加extern “C”,意思是告訴編譯器,將該函式按照C語言的規則來編譯。 比如:tcmalloc是google用C++實現的一個專案,他提供tcmallc()和tcfree兩個介面來使用,但如果是C專案就沒辦法使用,那麼他就使用extern “C”來解決。
上述舉例可能不太容易驗證,但我們可以通過一個簡單的反例來證明extern "C"的效果。
還是用上邊那個Add函式的例子來說明(我今天好像和這個Add函式過不去了,哈哈哈)
同樣不修改其他程式碼,Add函式的實現部分任然是刪除或者註釋狀態(總之就是沒有),而在其宣告處前加上extern “C”, 再次編譯連結檢視報錯資訊。
extern "C" int Add(int x, int y); //採用C語言的編譯規則
看到這裡,不需要再繼續解釋了吧!(本文完)
相關文章
- C++函式過載C++函式
- C++函式呼叫棧從何而來C++函式
- C++ 函式過載,函式模板和函式模板過載,選擇哪一個?C++函式
- C/C++—— C++中函式重寫和函式過載C++函式
- C++之函式過載C++函式
- C++的函式過載C++函式
- C/C++—— C++中建構函式不能是虛擬函式的原因分析C++函式
- C/C++語言新增“函式過載”功能簡單介紹和使用方法C++函式
- [ASM C/C++] C語言的main 函式ASMC++C語言AI函式
- C++ 函式過載和模板C++函式
- C++的函式的過載C++函式
- C++ 過載運算子和過載函式C++函式
- C語言函式手冊:c語言庫函式大全|C語言標準函式庫|c語言常用函式查詢C語言函式
- 為什麼 Python 不支援函式過載?Python函式
- C++入門教程(14):過載函式C++函式
- C++學習筆記-C++對C語言的函式擴充C++筆記C語言函式
- c語言中通過函式指標實現函式過載C語言函式指標
- 好你個C語言,原來還有這麼多副面孔!C語言
- C 語言到 C++ 過度C++
- 在定義C++, C通用介面函式時讓C++介面支援預設引數C++函式
- C++ 這個語句中[&]是什麼意思C++
- 118 C++中函式的過載C++函式
- C++ 獲取指定的過載函式地址C++函式
- c/c++語言函式 stat, fstat, lstat, fstatat - get file statusC++函式
- 開心檔之C++ 過載運算子和過載函式C++函式
- C語言 execve()函式C語言函式
- C語言常用函式C語言函式
- C語言的函式C語言函式
- C語言函式一本道來C語言函式
- C++行內函數、函式過載與函式預設引數C++函數函式
- C/C++—— C++中定義虛解構函式的原因C++函式
- c語言是如何處理函式呼叫的?C語言函式
- [ASM C/C++] C語言函式的可選性自變數ASMC++C語言函式變數
- C語言 itoa函式及atoi函式C語言函式
- 【c++】cout過載能不能寫成成員函式,若能,寫出函式原型,若不能,說明原因C++函式原型
- C語言函式呼叫棧C語言函式
- 詳解C語言函式C語言函式
- tmpnam() - C語言庫函式C語言函式