C語言下的函數(shù)名弊端
#include <stdio.h>
int Add(int a, int b)
{
return a + b;
}
我編寫了一個簡單的 Add 函數(shù)來執(zhí)行整數(shù)相加,它既簡單又能夠達到我想要的效果。然而,如果我現(xiàn)在需要一個能夠執(zhí)行浮點數(shù)相加的函數(shù)怎么辦呢?一種方法是重新編寫一個函數(shù),但是問題是該如何命名呢?已經(jīng)有一個 Add 函數(shù)了,如果取相同的名字就會出現(xiàn)命名沖突,而重新選擇名字又顯得繁瑣。為了解決這個問題,C++中引入了函數(shù)重載。
概念
函數(shù)重載:是函數(shù)的一種特殊情況,C++允許在同一作用域中聲明幾個功能類似的同名函數(shù),這些同名函數(shù)的形參列表(參數(shù)個數(shù)或類型或類型順序)不同,常用來處理實現(xiàn)功能類似數(shù)據(jù)類型不同的問題。
注意:對返回值沒有要求。如果返回值不同也能構成重載的話,那么當調(diào)用兩個無參且返回值不同的同名函數(shù)時,到底是調(diào)用哪一個函數(shù)編譯器并不清楚,會產(chǎn)生二義性。
參數(shù)類型不同
#include <iostream>
using namespace std;
int Add(int x, int y)
{
return x + y;
}
double Add(double x, double y)
{
return x + y;
}
int main()
{
printf("%d\n", Add(1, 2));
printf("%lf\n", Add(3.2, 2.8));
return 0;
}
這里兩個同名函數(shù)參數(shù)類型不同構成重載。
值得注意的是:在調(diào)用存在函數(shù)重載的函數(shù)時,傳入的參數(shù)類型要與期望調(diào)用的函數(shù)的參數(shù)類型匹配,否則可能會導致歧義。例如,下面的代碼中,調(diào)用 Add 函數(shù)時,傳入的參數(shù) 3.2 和 1 的類型分別為 double 和 int,可能導致編譯器無法確定應該調(diào)用哪個重載函數(shù)。在這個示例中,我們只需要屏蔽掉兩個 Add 函數(shù)中的一個就不會報錯了(會發(fā)生隱式類型轉(zhuǎn)化)。
?
參數(shù)個數(shù)不同
#include <iostream>
using namespace std;
void funcA()
{
cout << "funcA()" << endl;
}
void funcA(int a)
{
cout << a << endl;
}
int main()
{
funcA();
funcA(1);
return 0;
}
參數(shù)個數(shù)不同同樣構成重載。?
我在上一篇博客中介紹了缺省參數(shù):給傳參加上自動擋-CSDN博客?
可能你會好奇,如果上面第二個 funA 函數(shù)的形參加上缺省值能不能構成重載呢?我們來試下吧。
我們看到編譯器直接紅溫了。因為當調(diào)用無參的 funcA 函數(shù)時,編譯器不知道調(diào)用的是哪個函數(shù),可能你是真的無參,可能你是想用缺省值,這就造成了二義性。所以如果你寫了這樣的兩個函數(shù),當你調(diào)用時必須要傳參。這里也可以發(fā)現(xiàn)這兩個函數(shù)確實構成了重載,它們的參數(shù)個數(shù)確實是不同的。但是也只能調(diào)用帶參數(shù)的那個函數(shù),所以看似寫了兩個函數(shù),實際上只有一個是可以用的。因此這種重載的形式是沒意義的。
參數(shù)類型順序不同
#include <iostream>
using namespace std;
void fun(char a, int b)
{
cout << "fun(char a, int b)" << endl;
}
void fun(int a, char b)
{
cout << "fun(int a, char b)" << endl;
}
int main()
{
fun('a', 1);
fun(1, 'a');
return 0;
}
這里雖然兩個函數(shù)的參數(shù)類型都是一個 int 一個?char,但是順序不同,因此也構成重載。
為什么支持函數(shù)重載
到目前為止,你應該對C++中函數(shù)重載的條件和價值有了初步的了解。那么,為何C語言不支持這樣的高級功能,而C++卻能夠做到呢?下面我們將更深入地探討這個問題。
回顧一下,在C/C++中,程序的運行經(jīng)歷了四個關鍵步驟:
1. 預處理階段:在這個階段,主要完成宏替換、頭文件展開、注釋刪除以及條件編譯等操作。處理后會生成一個后綴為 .i 的文件,為后續(xù)編譯過程提供了基礎。
2.?編譯:在 .i 文件的基礎上進行語法檢查,包括語法分析、詞法分析、語義分析和符號匯總等操作。這個階段的輸出是一個匯編代碼文件(.s?文件)。值得強調(diào)的是,在生成匯編代碼時,函數(shù)的調(diào)用會被標記為一個 call?指令,其中存儲了該函數(shù)在代碼段的地址。當程序執(zhí)行到這個 call?指令時,會跳轉(zhuǎn)到相應的函數(shù)匯編代碼處,并開始創(chuàng)建函數(shù)棧幀以及執(zhí)行具體函數(shù)的實現(xiàn)。需要特別注意的是,如果函數(shù)的定義和聲明分離,這里的 call?指令并沒有具體的地址,只是先標記個名字,具體的函數(shù)地址會在最后的鏈接階段通過這個名字去查找。如果找不到函數(shù)定義,鏈接階段會報錯。
3. 匯編:將代碼翻譯成機器語言,簡單來說就是將?.s?文件中的匯編代碼轉(zhuǎn)換成二進制的機器碼并生成?.o 文件。并且會生成符號表,符號表記錄了程序中使用的各種符號(如變量、函數(shù)名、常量等)以及它們在內(nèi)存中的地址或其他相關信息。
4. 鏈接:將編譯生成的目標文件合并為可執(zhí)行文件。在鏈接階段,編譯器將解析符號引用,確定符號的地址,執(zhí)行重定位,最終生成一個包含所有必要信息的可執(zhí)行文件,以便在運行時被操作系統(tǒng)加載和執(zhí)行。這一過程確保了程序的各個部分正確連接,并能夠在內(nèi)存中協(xié)同工作。編譯中提到的鏈接不確定的函數(shù)地址就是在這一步驟中通過合并匯總后的符號表完成的。鏈接成功后,windows下生成后綴為 .exe的可執(zhí)行文件,Linux下則是默認生成?a.out 的可執(zhí)行文件。
現(xiàn)在你對這四個步驟已經(jīng)有一定的了解了,那么我們來解決如何尋找函數(shù)地址,也就是如何為函數(shù)命名的問題。這里以Linux下的 gcc 和 g++編譯器來展示C和C++在編譯階段是怎么為函數(shù)命名的。
首先來看 C++對應的 g++編譯器是怎么做的:?
這里我用 vim 寫了一段代碼,接下來我們看一下用 g++編譯過后的結果:
可以發(fā)現(xiàn)在Linux下,采用g++編譯完成后,函數(shù)名字的修飾發(fā)生改變,編譯器將函數(shù)參數(shù)類型信息添加到修改后的名字中。修飾過后的名字以 _Z 開頭,接著是函數(shù)名的字符個數(shù),然后是函數(shù)名,最后是參數(shù)類型的簡稱。這也就解釋了為什么函數(shù)重載支持參數(shù)個數(shù)、類型、類型順序的不同,但不支持返回值的不同,因為在這個命名規(guī)則中并未包含返回值信息。
接下來用同一段代碼我們來看看C語言編譯器也就是Linux下的gcc編譯器是怎么命名的:
注意到了吧,在Linux下使用gcc編譯后,函數(shù)名并沒有被修飾,仍然保持原始函數(shù)名。這也解釋了為什么C語言無法支持函數(shù)重載,因為同名函數(shù)將無法被編譯器區(qū)分。文章來源:http://www.zghlxwxcb.cn/news/detail-772865.html
windows下vs編譯器對函數(shù)名字修飾規(guī)則相對復雜難懂,但道理都是類似的,這里就不做細致的研究了,感興趣的話可以上網(wǎng)找一下相關資料。文章來源地址http://www.zghlxwxcb.cn/news/detail-772865.html
到了這里,關于【C++】函數(shù)重載詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!