天天看點

函數重載

函數重載

如果同一作用域内的幾個函數名字相同但形參清單不同,我們稱之為重載函數。例如:

這些函數接受的形參類型不一樣,但是執行的操作非常類似。當調用這些函數時,編譯器會根據傳遞的實參類型推斷想要的是哪個函數:

int j[2]={0,1};

print("hello world");   //調用void print(const char *cp);

print(j,end(j)-begin(j));  //調用void pring(const int ia[],size_t size);

print(begin(j),end(j));   //調用void print(const int *beg,const int *end);

函數的名字僅僅是讓編譯器知道它調用的是哪個函數,而函數重載可以在一定程度上減輕程式員起名字、記名字的負擔。

main函數不能重載

定義重載函數

對于重載的函數來說,它們應該在形參數量或形參類型上有所不同。

不允許兩個函數除了傳回類型外其他所有的要素都相同(因為隻有傳回類型不同,調用函數的時候不能區分調用的是哪一個函數)。假設有兩個函數,它們的形參清單一樣但是傳回類型不同,則第二個函數的聲明是錯誤的:

record lookup(const account&);

bool lookup(const account&);  //錯誤:與上一個函數相比隻有傳回類型不同

判斷兩個形參的類型是否相異

有時候兩個形參清單看起來不一樣,但實際上是相同的:

//每對聲明的是同一個函數

record lookup(const account &acct);

record lookup(const account &);  //省略了形參的名字

typedef phone telno;

record lookup(const phone&);

record lookup(const telno&);   //telno和phone的類型相同

在第一對聲明中,第一個函數給它的形參起了名字,第二個函數沒有。形參的名字僅僅起到幫助記憶的作用,有沒有它并不影響形參清單的内部。

第二對聲明看起來類型不同,但事實上telno不是一種新類型,他隻是phone的别名而已。類型别名為已存在的類型提供了另外一個名字,它并不是建立新類型。

重載和const形參

頂層const不影響傳入函數的對象,一個擁有頂層const的形參無法和另一個沒有頂層const的形參區分開來:

record lookup(phone);

record lookup(const phone);  //重複聲明了record lookup(phone);

record lookup(phone*);

record lookup(phone * const );   //重複聲明了record lookup (phone *);

在這兩組函數聲明中,每一組的第二個聲明和第一個聲明時等價的。

另一方面,如果形參是某種類型的指針或引用,則通過區分其指向的是常量對象還是非常量對象可以實作函數重載,此時的const是底層的:

//對于接受引用或指針的函數來說,對象時常量還是非常量對象的形參不同

//定義了4個獨立的重載函數

record lookup(account &);   //函數作用于account的引用

record lookup(const account &); //新函數,作用于常量引用

record lookup(account *);   //新函數,作用于指向account的指針

record lookup(const account *);  //新函數,作用于指向常量的指針

在上面的例子中,編譯器可以通過實參是否常量來推斷應該調用哪個函數。因為const不能轉換成其他類型,是以我們隻能把const對象(或指向const的指針)傳遞給const形參。相反的,因為非常量可以轉換成const,是以上面的4個函數都能作用于非常量對象或者指向非常量對象的指針。不過,當我們傳遞一個非常量對象或者指向非常量對象的指針時,編譯器會優先選用非常量版本的函數。

const_cast和重載

const_cast在重載函數的情景中最有用。

//比較兩個string對象的長度,傳回較短的那個引用

const string &shorterstring(const string &s1,const string &s2)

{

  return s1.size()<=s2.size()?s1:s2;

}

這個函數的參數和傳回值都是const string的引用。我們可以對兩個非常量的string實參調用這個函數,但傳回的結果仍然是const string的引用。是以我們需要一種新的shorterstring函數,當它的實參不是常量時,得到的結果是一個普通的引用,使用const_cast可以做到這一點:

string &shorterstring(string &s1,string &s2)

  auto &r=shorterstring(const_cast<const string&>(s1),const_cast<const string&>(s2));

  return const_cast<stirng&>(r);

在這個版本的函數中,首先将它的實參強制轉換成對const的引用,然後調用了shorterstring函數的const版本。const版本傳回到const string的引用,這個引用事實上綁定在了某個初始的非常量實參上。是以,我們可以再将其轉換回一個普通的string&,這顯然是安全的。

調用重載的函數

定義了一組重載函數後,我們需要以合理的實參調用它們。函數比對是指一個過程,在這個過程中我們把函數調用與一組重載函數中的某一個關聯起來,函數比對也叫做函數确定。編譯器首先将調用的實參與重載集合中每一個函數的形參進行比較,然後根據比較的結果決定到底調用哪個函數。

現在我們需要掌握的是,當調用重載函數時有三種可能的結果:

編譯器找到一個與實參最佳比對的函數,并生成調用該函數的代碼;

找不到任何一個函數與調用的實參比對,此時編譯器發出無比對的錯誤資訊;

又多于一個函數可以比對,但是每一個都不是明顯的最佳選擇。此時也将發生錯誤,稱為二義性調用。

重載與作用域

其實,重載對作用域的一般性質并沒有什麼改變:如果我們在内層作用域中聲明名字,它将隐藏外層作用域中聲明的同名實體。在不同的作用域中無法重載函數名:

當我們調用print函數時,編譯器首先尋找對該函數名的聲明,找到的是接受int值得哪個局部什麼。一旦在目前作用域中找到了所需的名字,編譯器就會忽略掉外層作用域中同名實體。剩下的工作就是檢查函數調用是否有效了。

在c++中,名字查找發生在類型檢查之前。

假設我們把print(int)和其他print函數聲明放在同一個作用域中,則它将成為另一種重載形式。此時,因為編譯器能看到所有三個函數,上述調用的處理結果将完全不同:

繼續閱讀