------------恢復內容開始------------
每一種現象背后都隱藏著一種本質,關鍵是要不要挖掘
前言:

函式多載的重要性還不清楚,但是你知道 C++中如何實作函式多載(雖然這篇文章是關于 C++中函式多載的實作,但我想其他語言也是如此)?這可分成以下兩個問題
在宣告/定義多載函式時如何解決命名沖突?撇開函式多載不談, using是一種解決命名沖突的方法,還有許多其他方法可以解決命名沖突,在此不再贅述,2、當我們呼叫一個多載函式時,如何進行決議?也就是說,如何知道呼叫了哪個函式呢?
所有支持函式多載的語言都必須解決這兩個問題!有了這兩個問題,我們就可以開始討論了,這篇論文是這樣的:
1.例題介紹了(現象)函式多載(what),為何函式多載(why)是必需的?編譯器如何解決命名沖突?為什么函式多載不考慮回傳值型別3、多載函式的呼叫與凌號匹配情況4、編譯器如何決議多載函式呼叫?依據一組可供選擇的函式名稱確定最佳匹配函式5、總結
1.介紹實體(現象)
1.什么是函式多載(what)?
“函式多載”是指在同一個范圍內,可以有一組具有相同函式名、不同串列引數的函式,這組函式叫做“多載函式”,重負載函式通常用于命名一組具有類似功能的函式,減少了函式名稱的數量,避免了名稱空間的污染,對程式的可讀性有很大的幫助,
When two or more different declarations are specified for a single name in the same scope, that name is said to overloaded. By extension, two declarations in the same scope that declare the same name but with different types are called overloaded declarations. Only function declarations can be overloaded; object and type declarations cannot be overloaded. ——摘自《ANSI C++ Standard. P290》
看看下面這個例子,體會一下:實作一個列印函式,它可以同時列印 int型別和字串型別,用 C++,我們可以做到這些:
#include using namespace std; void print(int i) { cout<<"print a integer :"<
在上述代碼的實作中, print (string)或 print (string)都可以根據具體 print ()的引數呼叫,以上 print (12)將呼叫 print (int),而 print (" hello world")將呼叫 print (string),其結果如下所示:

(先用 g++ test. c編譯,然后執行)1.2.為什么需要函式多載(why)?想象一下,如果沒有像 C那樣的函式多載機制,那么您必須這樣做:
給 print函式取一個不同的名字,比如print_int,print_string,在這里,有兩種情況,如果有很多種情況,則需要為實作相同功能的函式取許多名稱,比如添加列印 long型別, char*,各種型別的陣列,等等,這么做是有害的!
一個類有相同的類名,即:建構式都具有相同的名稱,在沒有函式多載機制的情況下,實體化不同物件是相當麻煩的!運算子多載,本質上是函式多載,它極大地豐富了現有運算子的意義,使+可以用來連接字串回憶,

接下來,我們將分析 C++如何實作函式多載機處理,如何編譯來解決命名沖突呢?要想理解編譯器是被放在檔案中的,查看匯編編編編下的匯編,我們將上面的編譯結果反編譯,查看匯編代碼(全文都是在 Linux下進行的實驗, Window在這里面可以參考《一個簡單的題目引發的思考》一文,舊版本中既用到 Linux下,這里就是匯編, Windows下就是匯編,兩者之間有區別),
我們執行命令objdump-d a. out> log. txt反匯編,然后重定向結果到 log. txt檔案,然后對 log. txt檔案進行分析,discovery函式 void print (int i)編型別已在編譯 id print (string str)之后型別,個數,回傳型別,看一下下面的映射引數串列: type、 type和 type,表格,看下圖:
數字的型別,數字數量,與回傳型別無關,但是,請查看下列映射:void print(int i) --> _Z5printi void print(string str) --> _Z5printSsZ5代表回傳值型別, print函式名, i代表整型 int, Ss代表 string字串,也就是被映射為回傳型別+函式名+引數串列,最終將通過_Z5printi,_Z5printSs在主函式內呼叫相應的函式:
80489bc: e8 73 ff ff ff call 8048934 <_Z5printi>………80489f0: e8 7a ff ff ff call 804896f <_Z5printSs>
另外,我們還會撰寫一些多載函式,以驗證猜測,例如:
void print(long l) --> _Z5printl void print(char str) --> _Z5printc很可能是int-> i,long-> l,char-> c,string-> Ss….基本上都是用首字母表示的,現在我們來看一下,如果一個函式的回傳值型別對它的變名有影響,比如:
#include using namespace std; int max(int a,int b) { return a>=b?a:b; } double max(double a,double b) { return a>=b?a:b; } int main() { cout<<"max int is: "
<int max (int a, int b)映射為_Z3maxii, double max (double a, double b)映射為_Z3maxdd,這證實了我的猜測,在 Z之后有各種回傳型別的數字代碼,更多細節的對應關系,比如,數字對應回傳型別,哪個字符代表哪個重引數型別,就不再詳細討論了,因為這是一個與編譯器相關的問題,
上面的研究都是基于 g++編譯器的,如果使用 vs編譯器,那么這種對應關系肯定與這個不同,但規則是相同的:“return+函式名+引數串列”,因為在映射機制中也有回傳型別,所以這種不同的回傳型別映射之后,函式名肯定會有所不同,但是為什么不在函式多載中考慮函式的回傳型別呢?
—這是為了保持決議操作符或函式呼叫與背景關系無關(與背景關系無關),請看下面這個示例
float sqrt(float); double sqrt(double); void f(double da, float fla) { float fl=sqrt(da);//呼叫sqrt(double) double d=sqrt(da);//呼叫sqrt(double) fl=sqrt(fla);//呼叫sqrt(float) d=sqrt(fla);//呼叫sqrt(float) }
假如回傳型別是在函式多載中考慮的,那么就不可能獨立于背景關系來決定呼叫哪個函式,到目前為止,看起來分析得很透徹,但是我們也漏掉了對函式多載的重要限定——作用域,前面介紹的所有函式多載都是全域函式,接下來我們將查看一個類,它呼叫 print函式與類的物件,并根據引數呼叫不同的函式:
#include using namespace std; class test{ public: void print(int i) { cout<<"int"<接下來,我們將看到此時 print函式映射之后的函式名:void print(int i) --> _ZN4test5printEivoid print(char c) --> _ZN4test5printEc請注意之前的N4test
我們很容易猜出應該代表范圍,N4可能代表名稱空間, test類名,等等,最精確的映射機制如下:范圍+回傳型別+函式名+引數串列對多載函式進行呼叫匹配既然多載函式的命名沖突問題已經得到解決,那么當定義完多載函式后,如何使用函式名呼叫它呢?
要估算哪一個多載函式是最合適的,需要按以下規則進行判斷:完全匹配:不進行轉換就進行引數匹配,或者只進行少量轉換,例如陣列名為指標,函式名為指標, T為 const T;提升匹配:

即整數提升(例如 bool為 int, char為 int, short為 int), float為 double使用標準轉換匹配:例如 int為 double, double為 int, double為 double, double為 long double, Derived*為 Base*, T*為 void*, int為 unsigned int;使用用戶定制匹配;使用省略號匹配:類似 printf中省略號引數若有多個匹配函式在最上層找到,呼叫就會被拒絕(因為模凌兩可),
查看以下示例:
void print (int); void print (const char*); void print (double); void print (long); void print (char); void h (char c, int i, short s, float f){print (c);//精確匹配,呼叫 print (char) print (i);//精確匹配,呼叫 print ('a');//精確匹配, print (49);//精確匹配,呼叫 print (int), print (0);//精確匹配,呼叫 print (double)*);
如下例所示,定義過少或過多的多載函式都可能導致模凌兩可,
void f1(char); void f1(long); void f2(char*); void f2(int*); void k(int i) { f1(i);//呼叫f1(char)? f1(long)? f2(0);//呼叫f2(char*)?f2(int*)? }
這個時候,侯編譯器會報錯,并把錯誤交給用戶自己來處理:通過顯示型別轉換來呼叫,等等(比如f2 (static_cast< int*>(0)),當然,這樣做很難看,并且您希望呼叫其他方法來進行轉換),以上示例只是一個引數,接下來我們將看到另外兩個引數的情形:
int pow(int ,int); double pow(double,double); void g() { double d=pow(2.0,2)//呼叫pow(int(2.0),2)? pow(2.0,double(2))? }
編譯器如何對多載函式呼叫進行決議?當編譯器實作呼叫多載函式的決議機制時,肯定要先找到一些同名的候選函式,然后再從候選函式中找到最符合的,如果沒有找到,就會報錯,

以下是多載函式決議的一種方法:編譯器用語法分析、 C++文法、符號表、抽象語法樹等互動方式處理多載函式呼叫,互動圖大致如下:它下面是這4個決議步驟完成的內容:獲取函式名稱,獲取函式的所有引數運算式型別,由匹配文法中的函式呼叫;
語法分析程式查找多載函式,在符號表中進行多載決議,回傳最佳函式語法分析程式創建抽象語法樹,將符號表中存盤的最佳函式系結到抽象語法樹下一步,我們將著重于解釋多載決議,它滿足前面“3,多載函式呼叫匹配”中所述的匹配順序和匹配規則,
重函式的決議大致分為三個步驟:基于函式名確定候選函式集從候選函式集選擇可用函式集從可用函式集確定最佳函式,或者由于模凌兩可回傳錯誤4.1、根據函式名確定一組候選函式取決于該函式在同一范圍內的所有名稱相同的函式,并且要求可見(如 private, protected, public, friend等),
在函式多載的定義中,“相同范圍”也是一種限定,如果不在某個范圍內,則不算函式多載,如以下代碼:void f (int); void g (){void f (double); f (1);//在此,應使用 f (double)代替 f (int)}也就是,內部范圍的函式將隱藏外部范圍的同名函式!相同派生類的成員函式對基類隱藏具有相同名稱的函式,這樣可以理解,對變數的訪問也是如此,例如,用“::”限定一個函式中具有相同名稱的變數訪問全域,
對于候選函式集的查找,通常采用深度優化搜索演算法:step1:從一個函式呼叫點開始查找,查找可以逐層作用域外部可見的候選者step2:如果前一步收集的不在用戶自定義名稱空間中,則使用 using機制引入的名稱空間中的候選器,否則結束當收集候選函式時,如果呼叫函式的引數型別是非結構體型別,則該候選函式只包含呼叫點可見的函式;

如果呼叫函式的引數型別包括型別別物件、型別別指標、型別別參考或指向類成員的指標,則該候選函式為以下且:(1)在呼叫點可見的函式;(2)在定義此型別別的名稱空間中或在定義此類的基類的名稱空間中宣告的函式;
(3)對此類或其基類使用友元函式;下一步,我們將通過一個簡單的例子:
void f(); void f(int); void f(double, double = 314); names pace N { void f(char3 ,char3); } classA{ public: operat or double() { } }; int main ( ) { using names pace N; //using指示符 A a f(a); return 0; }
按照上面的方法,因為引數是型別別向,收集候選器函式分為3個步驟:
(1)從函式呼叫所在的 main函式范圍內開始查找函式 f的宣告,但未找到結果,至主函式范圍的外部范圍查找,這個次序是類及其基類的友元函式,把他們放到一個候選集合中;
(2 named. sing)指示符所指稱的 named N (char3,char3);

(3)考慮類別2的集合,一種是定義此型別別的名稱空間,或者在定義該類的基類的名稱空間中的一個函式字;第二個是該類或它的基類的友元函式,這種情況下,這2個類集合是空的,最后的候選集是上面列出的4個函式 f,4.2.確定可用功能可用函式表示:函式引數數目匹配且每個引數都有一個隱式轉換序列,
點擊了解更多資料,更有免費開源專案和課程等你觀看哦!
------------恢復內容結束------------
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/184136.html
標籤:其他
上一篇:Java static 關鍵字
