关于重载函数的一些学习
- 重載函數
出現在相同作用域中的兩個函數,如果具有相同的名字而形參表不同,則稱為重載函數。
使用某種程序設計語言編寫過算術表達式的程序員都肯定使用過重載函數。表達式
1 + 3調用了針對整型操作數加法操作符,而表達式
1.0 + 3.0調用了另外一個專門處理浮點操作數的不同的加法操作。根據操作數的類型來區分不同的操作,并應用適當的操作,是編譯器的責任,而不是程序員的事情。
類似地,程序員可以定義一組函數,它們執行同樣的一般性動作,但是應用在不同的形參類型上,調用這些函數時,無需擔心調用的是哪個函數,就像我們不必操心執行的是整數算術操作還是浮點數自述操作就可以實現 int 型加法或 double 型加法一樣。
通過省去為函數起名并記住函數名字的麻煩,函數重載簡化了程序的實現,使程序更容易理解。函數名只是為了幫助編譯器判斷調用的是哪個函數而已。例如,一個數據庫應用可能需要提供多個 lookup 函數,分別實現基于姓名、電話號碼或賬號之類的查詢功能。函數重載使我們可以定義一系列的函數,它們的名字都是 lookup,不同之處在于用于查詢的值不相同。如此可傳遞幾種類型中的任一種值調用 lookup 函數:
Record lookup(const Account&); // find by AccountRecord lookup(const Phone&); // find by PhoneRecord lookup(const Name&); // find by Name Record r1, r2;r1 = lookup(acct); // call version that takes an Accountr2 = lookup(phone); // call version that takes a Phone這里的三個函數共享同一個函數名,但卻是三個不同的函數。編譯器將根據所傳遞的實參類型來判斷調用的是哪個函數。
要理解函數重載,必須理解如何定義一組重載函數和編譯器如何決定對某一調用使用哪個函數。任何程序都僅有一個 main 函數的實例。main 函數不能重載。
- 重載函數和重復聲明的區別
如果兩個函數聲明的返回類型和形參表完全匹配,則將第二個函數聲明視為第一個的重復聲明。如果兩個函數的形參表完全相同,但返回類型不同,則第二個聲明是錯誤的:
Record lookup(const Account&);bool lookup(const Account&); // error: only return type is different函數不能僅僅基于不同的返回類型而實現重載。
注意,有些看起來不相同的形參表本質上是相同的:
Record lookup(const Account &acct);Record lookup(const Account&);形參名只是幫助文檔,并沒有修改形參列表。
typedef Phone Telno;Record lookup(const Phone&);Record lookup(const Telno&);看似形參類型不同,但注意到 Telno 其實并不是新類型,只是 Phone 類型的同義詞。typedef 給已存在的數據類型提供別名,但并沒有創建新的數據類型。所以,如果兩個形參的差別只是一個使用 typedef 定義的類型名,而另一個使用 typedef 對應的原類型名,則這兩個形參并無不同。
Record lookup(const Phone&, const Name&);// default argument doesn't change the number of parametersRecord lookup(const Phone&, const Name& = "");形參列表只有默認實參不同。默認實參并沒有改變形參的個數(而且參數的類型也都相同)。無論實參是由用戶還是由編譯器提供的,這個函數都帶有兩個實參。
Record lookup(Phone);Record lookup(const Phone);最后一對的區別僅在于是否將形參定義為 const。這種差異并不影響傳遞至函數的對象;第二個函數聲明被視為第一個的重復聲明。其原因在于實參傳遞的方式。復制形參時并不考慮形參是否為 const——函數操縱的只是副本。函數的無法修改實參。結果,既可將 const 對象傳遞給 const 形參,也可傳遞給非 const 形參,這兩種形參并無本質區別。
值得注意的是,形參與 const 形參的等價性僅適用于非引用形參。有 const 引用形參的函數與有非 const 引用形參的函數是不同的。類似地,如果函數帶有指向 const 類型的指針形參,則與帶有指向相同類型的非 const 對象的指針形參的函數不相同。//正如《關于函數形參的一些討論》所討論到的,void fcn(const int i) { } 和void fcn(int i) { }會被視為重定義。 因為當你給fcn傳遞參數時,會拷貝傳遞的參數生成一個副本,而fcn是修改的那個副本而非原對象,所以這里的const沒有限制,視void fcn(const int i) { } 和void fcn(int i) { }為相同。而對于const引用和非const引用,如void f(cosnt int &r)和void f(int &r),如果f函數直接操作的是你給f傳遞的對象本身,所以如果傳遞的對象是const,就不能用void f(int &r),只能用void f(cosnt int &r),所以void f(cosnt int &r)和void f(int &r)是不同的。
- 重載與作用域
如果局部地聲明一個函數,則該函數將屏蔽而不是重載在外層作用域中聲明的同名函數。由此推論,每一個版本的重載函數都應在同一個作用域中聲明。
作為例子,考慮下面的程序:
void print(const string &);void print(double); // overloads the print functionvoid fooBar(int ival){void print(int); // new scope: hides previous instances of printprint("Value: "); // error: print(const string &) is hiddenprint(ival); // ok: print(int) is visibleprint(3.14); // ok: calls print(int); print(double) is hidden}函數 fooBar 中的 print(int) 聲明將屏蔽 print 的其他聲明,就像只有一個有效的 print 函數一樣:該函數僅帶有一個 int 型形參。在這個作用域或嵌套在這個作用域里的其他作用域中,名字 print 的任何使用都將解釋為這個 print 函數實例。調用 print 時,編譯器首先檢索這個名字的聲明,找到只有一個 int 型形參的 print 函數的局部聲明。一旦找到這個名字,編譯器將不再繼續檢查這個名字是否在外層作用域中存在,即編譯器將認同找到的這個聲明即是程序需要調用的函數,余下的工作只是檢查該名字的使用是否有效。第一個函數調用傳遞了一個字符串字面值,但是函數的形參卻是 int 型的。字符串字面值無法隱式地轉換為 int 型,因而該調用是錯誤的。print(const string&) 函數與這個函數調用匹配,但已被屏蔽,因此不在解釋該調用時考慮。當傳遞一個 double 數據調用 print 函數時,編譯器重復了同樣的匹配過程:首先檢索到 print(int) 局部聲明,然后將 double 型的實參隱式轉換為 int 型。因此,該調用合法。
另一種情況是,在與其他 print 函數相同的作用域中聲明 print(int),這樣,它就成為 print 函數的另一個重載版本。此時,所有的調用將以不同的方式解釋:
void print(const string &);void print(double); // overloads print functionvoid print(int); // another overloaded instancevoid fooBar2(int ival){print("Value: "); // ok: calls print(const string &)print(ival); // ok: print(int)print(3.14); // ok: calls print (double)}現在,編譯器在檢索名字 print 時,將找到這個名字的三個函數。每一個調用都將選擇與其傳遞的實參相匹配的 print 版本。
- 重載的確定
函數重載確定,即函數匹配是將函數調用與重載函數集合中的一個函數相關聯的過程。通過自動提取函數調用中實際使用的實參與重載集合中各個函數提供的形參做比較,編譯器實現該調用與函數的匹配。
考慮下面的這組函數和函數調用:
void f();void f(int);void f(int, int);void f(double, double = 3.14);f(5.6); // calls void f(double, double)第一步,確定候選函數。函數重載確定的第一步是確定該調用所考慮的重載函數集合,該集合中的函數稱為候選函數。候選函數是與被調函數同名的函數,并且在調用點上,它的聲明可見。在這個例子中,有四個名為 f 的候選函數。
第二步,選擇可行函數。從候選函數中選擇一個或多個函數,它們能夠用該調用中指定的實參來調用。因此,選出來的函數稱為可行函數??尚泻瘮当仨殱M足兩個條件:第一,函數的形參個數與該調用的實參個數相同;第二,每一個實參的類型必須與對應形參的類型匹配,或者可被隱式轉換為對應的形參類型。
如果函數具有默認實參,則調用該函數時,所用的實參可能比實際需要的少。默認實參也是實參,在函數匹配過程中,它的處理方式與其他實參一樣。
對于函數調用 f(5.6),可首先排除兩個實參個數不匹配的候選函數。沒有形參的 f 函數和有兩個 int 型形參的 f 函數對于這個函數調用來說都不可行。例中的調用只有一個實參,而這些函數分別帶有零個和兩個形參。
另一方面,有兩個 double 型參數的 f 函數可能是可行的。調用帶有默認實參的函數時可忽略這個實參。編譯器自動將默認實參的值提供給被忽略的實參。因此,某個調用擁有的實參可能比顯式給出的多。
根據實參個數選出潛在的可行函數后,必須檢查實參的類型是否與對應的形參類型匹配。與任意函數調用一樣,實參必須與它的形參匹配,它們的類型要么精確匹配,要么實參類型能夠轉換為形參類型。在這個例子中,余下的兩個函數都是是可行的。
f(int) 是一個可行函數,因為通過隱式轉換可將函數調用中的 double 型實參轉換為該函數唯一的 int 型形參。f(double, double) 也是一個可行函數,因為該函數為其第二個形參提供了默認實參,而且第一個形參是 double 類型,與實參類型精確匹配。如果沒有找到可行函數,則該調用錯誤。
第三步,尋找最佳匹配。函數重載確定的第三步是確定與函數調用中使用的實際參數匹配最佳的可行函數。這個過程考慮函數調用中的每一個實參,選擇對應形參與之最匹配的一個或多個可行函數。這里所謂“最佳”,其原則是實參類型與形參類型越接近則匹配越佳。因此,實參類型與形參類型之間的精確類型匹配比需要轉換的匹配好。
- 調用的二義性
如果函數調用使用了兩個或兩個以上的顯式實參,則函數匹配會更加復雜。假設有兩樣的名為 f 的函數,分析下面的函數調用:
f(42, 2.56);可行函數將以同樣的方式選出。編譯器將選出形參個數和類型都與實參匹配的函數。在本例中,可行函數是 f(int, int) 和 f(double, double)。接下來,編譯器通過依次檢查每一個實參來決定哪個或哪些函數匹配最佳。如果有且僅有一個函數滿足下列條件,則匹配成功:
其每個實參的匹配都不劣于其他可行函數需要的匹配。
至少有一個實參的匹配優于其他可行函數提供的匹配。
如果在檢查了所有實參后,仍找不到唯一最佳匹配函數,則該調用錯誤。編譯器將提示該調用具有二義性。
在本例子的調用中,首先分析第一個實參,發現函數 f(int, int) 匹配精確。如果使之與第二個函數匹配,就必須將 int 型實參 42 轉換為 double 型的值。通過內置轉換的匹配“劣于”精確匹配。所以,如果只考慮這個形參,帶有兩個 int 型形參的函數比帶有兩個 double 型形參的函數匹配更佳。
但是,當分析第二個實參時,有兩個 double 型形參的函數為實參 2.56 提供了精確匹配。而調用兩個 int 型形參的 f 函數版本則需要把 2.56 從 double 型轉換為 int 型。所以只考慮第二個形參的話,函數 f(double, double) 匹配更佳。
因此,這個調用有二義性:每個可行函數都對函數調用的一個實參實現更好的匹配。編譯器將產生錯誤。解決這樣的二義性,可通過顯式的強制類型轉換強制函數匹配:
f(static_cast<double>(42), 2.56); // calls f(double, double)f(42, static_cast<int>(2.56)); // calls f(int, int)- 需要類型提升或轉換的匹配
必須注意的一個重點即便是較小的整型也會提升為 int 型而不會直接提升為short。假設有兩個函數,一個的形參為 int 型,另一個的形參則是 short 型。對于任意整型的實參值,int 型版本都是優于 short 型版本的較佳匹配,即使從形式上看 short 型版本的匹配較佳:
void ff(int);void ff(short);ff('a'); // char promotes to int, so matches f(int)字符字面值是 char 類型,char 類型可提升為 int 型。提升后的類型與函數 ff(int) 的形參類型匹配。char 類型同樣也可轉換為 short 型,但需要類型轉換的匹配“劣于”需要類型提升的匹配。結果應將該調用解釋為對 ff (int) 的調用。
通過類型提升實現的轉換優于其他標準轉換。例如,對于 char 型實參來說,有 int 型形參的函數是優于有 double 型形參的函數的較佳匹配。其他的標準轉換也以相同的規則處理。例如,從 char 型到 unsigned char 型的轉換的優先級不比從 char 型到 double 型的轉換高。再舉一個具體的例子,考慮:
extern void manip(long);extern void manip(float);manip(3.14); // error: ambiguous call字面值常量 3.14 的類型為 double。這種類型既可轉為 long 型也可轉為 float 型。由于兩者都是可行的標準轉換,因此該調用具有二義性。沒有哪個標準轉換比其他標準轉換具有更高的優先級。
- 枚舉類型
枚舉類型的對象只能用同一枚舉類型的另一個對象或一個枚舉成員進行初始化。整數對象即使具有與枚舉元素相同的值也不能用于調用期望獲得枚舉類型實參的函數。
enum Tokens {INLINE = 128, VIRTUAL = 129};void ff(Tokens);void ff(int);int main() {Tokens curTok = INLINE;ff(128); // exactly matches ff(int)ff(INLINE); // exactly matches ff(Tokens)ff(curTok); // exactly matches ff(Tokens)return 0;}雖然無法將整型值傳遞給枚舉類型的形參,但可以將枚舉值傳遞給整型形參。此時,枚舉值被提升為 int 型或更大的整型。具體的提升類型取決于枚舉成員的值。如果是重載函數,枚舉值提升后的類型將決定調用哪個函數:
void newf(unsigned char);void newf(int);unsigned char uc = 129;newf(VIRTUAL); // calls newf(int)newf(uc); // calls newf(unsigned char)枚舉類型 Tokens 只有兩個枚舉成員,最大的值為 129。這個值可以用 unsigned char 類型表示,很多編譯器會將這個枚舉類型存儲為 unsigned char 類型。然而,枚舉成員 VIRTUAL 卻并不是 unsigned char 類型。就算枚舉成員的值能存儲在 unsigned char 類型中,枚舉成員和枚舉類型的值也不會提升為 unsigned char 類型。
在使用有枚舉類型形參的重載函數時,請記住:由于不同枚舉類型的枚舉常量值不相同,在函數重載確定過程中,不同的枚舉類型會具有完全不同的行為。其枚舉成員決定了它們提升的類型,而所提升的類型依賴于機器。
- const形參
僅當形參是引用或指針時,形參是否為 const 才有影響??苫诤瘮档囊眯螀⑹侵赶?const 對象還是指向非 const 對象,實現函數重載。將引用形參定義為 const 來重載函數是合法的,因為編譯器可以根據實參是否為 const 確定調用哪一個函數:
Record lookup(Account&);Record lookup(const Account&); // new functionconst Account a(0);Account b;lookup(a); // calls lookup(const Account&)lookup(b); // calls lookup(Account&)如果形參是普通的引用,則不能將 const 對象傳遞給這個形參。如果傳遞了 const 對象,則只有帶 const 引用形參的版本才是該調用的可行函數。如果傳遞的是非 const 對象,則上述任意一種函數皆可行。非 const 對象既可用于初始化 const 引用,也可用于初始化非 const 引用。但是,將 const 引用初始化為非 const 對象,需通過轉換來實現,而非 const 形參的初始化則是精確匹配。
對指針形參的相關處理如出一轍??蓪?const 對象的地址值只傳遞給帶有指向 const 對象的指針形參的函數。也可將指向非 const 對象的指針傳遞給函數的 const 或非 const 類型的指針形參。如果兩個函數僅在指針形參時是否指向 const 對象上不同,則指向非 const 對象的指針形參對于指向非 const 對象的指針(實參)來說是更佳的匹配。重復強調,編譯器可以判斷:如果實參是 const 對象,則調用帶有 const* 類型形參的函數;否則,如果實參不是 const 對象,將調用帶有普通指針形參的函數。
注意,不能基于指針本身是否為 const 來實現函數的重載:
f(int *);f(int *const); // redeclaration此時,const 用于修改指針本身,而不是修飾指針所指向的類型。在上述兩種情況中,都復制了指針,指針本身是否為 const 并沒有帶來區別。當形參以副本傳遞時,不能基于形參是否為 const 來實現重載。
轉載于:https://www.cnblogs.com/predator-wang/p/5197971.html
總結
以上是生活随笔為你收集整理的关于重载函数的一些学习的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: epoll的LT和ET模式
- 下一篇: windows下使用net-snmp实现