1. 程式人生 > >函式過載之函式的預設引數

函式過載之函式的預設引數

昨天面試的時候,被問到函式過載時第三個引數為空,只有兩個引數會不會呼叫三個引數的函式,我脫口而出就說不會,回來想想感覺好像不對,然後查了一下資料,發現真的錯了

例子:int fun(int a,int b,int c=0) 

 fun(5,8)是可以呼叫上面那個函式的,但如果又存在一個函式int fun(int a,int b)

則,fun(5,8)因不知道呼叫哪個會出錯。

詳解如下:

一.形參&實參

形參和實參,雖然用了這麼久了,不過概念上還是有點糾結的。這裡簡單總結一下:形參是說明引數型別的,實參就是函式實際操作的物件,我們定義一個函式的時候,寫的那個是形參,我們呼叫函式的時候,給如的引數就是實參。

最近在百度知道上看到了一個關於形參實參最精闢的解釋,無恥的引用一下:

比如說進女廁所,那就是女人才能進去 ,那麼女人就是進女廁所這個操作的形參,林黛玉進去了,楊貴妃進去了,林黛玉,楊貴妃這些就是實參,李隆基要進的話那就型別不符

二.簡單使用

C++函式支援預設引數,這是一個很方便的特性。我們在函式宣告或者定義的時候,給函式的引數設定一個預設值,當呼叫時如果不給引數或者給出一部分引數,那麼就使用函式設定的預設引數值。先看一個例子:

  1. // C++Test.cpp : 定義控制檯應用程式的入口點。
  2. //
  3. #include "stdafx.h"
  4. #include <iostream>
  5. usingnamespace std;  
  6. void DefaultArguTest(int arg1, int arg2 = 2, int arg3 = 3)  
  7. {  
  8.     cout<<arg1<<" "<<arg2<<" "<<arg3<<endl;  
  9. }  
  10. int _tmain(int argc, _TCHAR* argv[])  
  11. {  
  12.     //第2,3個引數給出了,則使用引數的值
  13.     cout<<"No Default argu:"<<endl;  
  14.     DefaultArguTest(1,1,1);  
  15.     //第3個引數沒給出,則使用預設值
  16.     cout<<"Default argu3:"<<endl;  
  17.     DefaultArguTest(1,1);  
  18.     //第2,3個引數都沒給出,使用預設值
  19.     cout<<"Default argu2,3:"<<endl;  
  20.     DefaultArguTest(1);  
  21.     system("pause");  
  22.     return 0;  
  23. }  
結果:

No Default argu:
1 1 1
Default argu3:
1 1 3
Default argu2,3:
1 2 3
請按任意鍵繼續. . .

三.注意事項

感覺預設引數的知識點還是挺簡單的,但是要注意的地方還是有不少的... 1.一般預設引數給出的位置都是函式的宣告處,如果函式沒有宣告只有定義的時候,那就放在定義處。但是,如果函式有宣告,那麼就必須放在宣告處。如果放在了定義的地方,那麼會報出下面的錯誤: error C2660: “DefaultArguTest”: 函式不接受 2 個引數 表明編譯器並不知道給出了預設引數,仍按照我們輸入引數個數不對處理的。而如果我們在宣告和定義的地方都給出了預設引數也是不對的,會報出下面的錯誤: DefaultArguTest”: 重定義預設引數 : 引數 3 所以,我們簡單幹脆的記住:預設引數放在函式的宣告處! 2.如果左邊的引數給出了預設引數,那麼它右邊的引數必須都有預設引數。這個地方也是容易犯錯誤的地方。 3.呼叫實參必須是連續的,即我們給出的引數,必須從左只有填入形參中,而右邊沒給的才用預設引數來補齊。 4.預設值可以是全域性變數、全域性常量,甚至是一個函式。但不可以是區域性變數。因為預設引數的呼叫是在編譯時確定的,而區域性變數位置與預設值在編譯時無法確定。

四.預設引數和函式過載的衝突

預設引數和函式過載一起使用會導致衝突,看下面的例子:
  1. // C++Test.cpp : 定義控制檯應用程式的入口點。
  2. //
  3. #include "stdafx.h"
  4. #include <iostream>
  5. usingnamespace std;  
  6. //函式宣告
  7. void DefaultArguTest(int arg1 = 1, int arg2 = 2, int arg3 = 3);  
  8. //過載
  9. void DefaultArguTest();  
  10. int _tmain(int argc, _TCHAR* argv[])  
  11. {  
  12.     //不給引數
  13.     DefaultArguTest();  
  14.     system("pause");  
  15.     return 0;  
  16. }  
  17. //函式定義
  18. void DefaultArguTest(int arg1, int arg2, int arg3)  
  19. {  
  20.     cout<<arg1<<" "<<arg2<<" "<<arg3<<endl;  
  21. }  

錯誤如下:

 error C2668: “DefaultArguTest”: 對過載函式的呼叫不明確
1> 可能是“void DefaultArguTest(void)”
1> 或       “void DefaultArguTest(int,int,int)”

對於當我們不給引數的時候,預設的DefaultArguTest和無引數的DefaultArguTest都可能被呼叫,所以就造成了呼叫不明確的錯誤。

仔細想一下,為什麼C++的預設建構函式在我們自己定義了建構函式就自動不生成了呢?

個人感覺,有可能是害怕我們自己定義建構函式時,如果加上預設引數,那麼就和編譯器為我們提供的預設建構函式衝突了,為了防止這種隱患,索性如果自己寫了建構函式,那就不生成預設構造函數了。

五.覆寫函式時不要更換預設引數

如果我沒記錯的話這是《Effectice C++》中的一條,我們在覆寫函式的時候,絕對不能修改它的預設引數,因為這會導致一個非常難發現的BUG!正因為如此,我們如果在VS(帶VA外掛的,本人猜測這個是VA外掛加入的)中覆寫帶有預設引數的成員函式時,它會預設的將預設引數給出,以註釋的形式給出提醒:
  1. // C++Test.cpp : 定義控制檯應用程式的入口點。
  2. //
  3. #include "stdafx.h"
  4. #include <iostream>
  5. #include <string>
  6. usingnamespace std;  
  7. class Base  
  8. {  
  9. public:  
  10.     virtualvoid Print(int i = 1, int j = 2)  
  11.     {  
  12.         cout<<"In base: "<<i<<" "<<j<<endl;  
  13.     }  
  14. };  
  15. class Child : public Base  
  16. {  
  17.     //我們覆寫帶有預設引數的函式,VA外掛給出了提醒,這兩個值都是有預設引數的
  18.     void Print(int i /* = 1 */int j /* = 2 */)  
  19.     {  
  20.         cout<<"In Child: "<<i<<" "<<j<<endl;  
  21.     }  
  22. };  
  23. int _tmain(int argc, _TCHAR* argv[])  
  24. {  
  25.     Base* base = new Child();  
  26.     base->Print();  
  27.     system("pause");  
  28.     return 0;  
  29. }  
結果: In Child: 1 2
請按任意鍵繼續. . .

但是,如果我們不信邪,偏偏要給子類加一個不同的預設引數,結果就會大大出乎我們的意料:

  1. // C++Test.cpp : 定義控制檯應用程式的入口點。
  2. //
  3. #include "stdafx.h"
  4. #include <iostream>
  5. #include <string>
  6. usingnamespace std;  
  7. class Base  
  8. {  
  9. public:  
  10.     virtualvoid Print(int i = 1, int j = 2)  
  11.     {  
  12.         cout<<"In base: "<<i<<" "<<j<<endl;  
  13.     }  
  14. };  
  15. class Child : public Base  
  16. {  
  17. public:  
  18.     //我們手動的將預設引數修改了
  19.     void Print(int i = 3, int j  = 4 )  
  20.     {  
  21.         cout<<"In Child: "<<i<<" "<<j<<endl;  
  22.     }  
  23. };  
  24. int _tmain(int argc, _TCHAR* argv[])  
  25. {  
  26.     //靜態繫結
  27.     cout<<"Static bind:"<<endl;  
  28.     Child* child = new Child();  
  29.     child->Print();  
  30.     //動態繫結
  31.     cout<<"Dynamic bind:"<<endl;  
  32.     Base* base = new Child();  
  33.     base->Print();  
  34.     system("pause");  
  35.     return 0;  
  36. }  
結果:

Static bind:
In Child: 3 4
Dynamic bind:
In Child: 1 2
請按任意鍵繼續. . .

第一個沒有問題,子類指標呼叫子類函式,輸出的結果也是子類給出的預設引數。但是,第二個問題就大了,我們明明觸發了多型,但是,輸出的結果竟然是基類給出的那兩個預設引數的值!!!

為什麼會這樣?因為為了效率,函式的預設引數是使用靜態繫結的,換句話說,不管你有沒有多型,我只關心你用什麼指標來調,基類指標就呼叫基類的預設引數,子類指標就給出子類的預設引數。而不像我們多型那樣,會發生動態繫結,可以用基類指標呼叫子類函式。而我們在一個動態繫結的函式中使用了靜態繫結的引數,結果肯定是不對的!

所以,正如《Effective C++》中所說:“絕不重新定義繼承而來的預設引數”!