函式過載之函式的預設引數
昨天面試的時候,被問到函式過載時第三個引數為空,只有兩個引數會不會呼叫三個引數的函式,我脫口而出就說不會,回來想想感覺好像不對,然後查了一下資料,發現真的錯了
例子:int fun(int a,int b,int c=0)
fun(5,8)是可以呼叫上面那個函式的,但如果又存在一個函式int fun(int a,int b)
則,fun(5,8)因不知道呼叫哪個會出錯。
詳解如下:
一.形參&實參
形參和實參,雖然用了這麼久了,不過概念上還是有點糾結的。這裡簡單總結一下:形參是說明引數型別的,實參就是函式實際操作的物件,我們定義一個函式的時候,寫的那個是形參,我們呼叫函式的時候,給如的引數就是實參。
最近在百度知道上看到了一個關於形參實參最精闢的解釋,無恥的引用一下:
比如說進女廁所,那就是女人才能進去 ,那麼女人就是進女廁所這個操作的形參,林黛玉進去了,楊貴妃進去了,林黛玉,楊貴妃這些就是實參,李隆基要進的話那就型別不符
二.簡單使用
C++函式支援預設引數,這是一個很方便的特性。我們在函式宣告或者定義的時候,給函式的引數設定一個預設值,當呼叫時如果不給引數或者給出一部分引數,那麼就使用函式設定的預設引數值。先看一個例子:
- // C++Test.cpp : 定義控制檯應用程式的入口點。
- //
- #include "stdafx.h"
-
#include <iostream>
- usingnamespace std;
- void DefaultArguTest(int arg1, int arg2 = 2, int arg3 = 3)
- {
- cout<<arg1<<" "<<arg2<<" "<<arg3<<endl;
- }
- int _tmain(int argc, _TCHAR* argv[])
- {
- //第2,3個引數給出了,則使用引數的值
- cout<<"No Default argu:"<<endl;
-
DefaultArguTest(1,1,1);
- //第3個引數沒給出,則使用預設值
- cout<<"Default argu3:"<<endl;
- DefaultArguTest(1,1);
- //第2,3個引數都沒給出,使用預設值
- cout<<"Default argu2,3:"<<endl;
- DefaultArguTest(1);
- system("pause");
- return 0;
- }
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.預設值可以是全域性變數、全域性常量,甚至是一個函式。但不可以是區域性變數。因為預設引數的呼叫是在編譯時確定的,而區域性變數位置與預設值在編譯時無法確定。四.預設引數和函式過載的衝突
預設引數和函式過載一起使用會導致衝突,看下面的例子:- // C++Test.cpp : 定義控制檯應用程式的入口點。
- //
- #include "stdafx.h"
- #include <iostream>
- usingnamespace std;
- //函式宣告
- void DefaultArguTest(int arg1 = 1, int arg2 = 2, int arg3 = 3);
- //過載
- void DefaultArguTest();
- int _tmain(int argc, _TCHAR* argv[])
- {
- //不給引數
- DefaultArguTest();
- system("pause");
- return 0;
- }
- //函式定義
- void DefaultArguTest(int arg1, int arg2, int arg3)
- {
- cout<<arg1<<" "<<arg2<<" "<<arg3<<endl;
- }
錯誤如下:
error C2668: “DefaultArguTest”: 對過載函式的呼叫不明確
1> 可能是“void DefaultArguTest(void)”
1> 或 “void DefaultArguTest(int,int,int)”
對於當我們不給引數的時候,預設的DefaultArguTest和無引數的DefaultArguTest都可能被呼叫,所以就造成了呼叫不明確的錯誤。
仔細想一下,為什麼C++的預設建構函式在我們自己定義了建構函式就自動不生成了呢?
個人感覺,有可能是害怕我們自己定義建構函式時,如果加上預設引數,那麼就和編譯器為我們提供的預設建構函式衝突了,為了防止這種隱患,索性如果自己寫了建構函式,那就不生成預設構造函數了。
五.覆寫函式時不要更換預設引數
如果我沒記錯的話這是《Effectice C++》中的一條,我們在覆寫函式的時候,絕對不能修改它的預設引數,因為這會導致一個非常難發現的BUG!正因為如此,我們如果在VS(帶VA外掛的,本人猜測這個是VA外掛加入的)中覆寫帶有預設引數的成員函式時,它會預設的將預設引數給出,以註釋的形式給出提醒:- // C++Test.cpp : 定義控制檯應用程式的入口點。
- //
- #include "stdafx.h"
- #include <iostream>
- #include <string>
- usingnamespace std;
- class Base
- {
- public:
- virtualvoid Print(int i = 1, int j = 2)
- {
- cout<<"In base: "<<i<<" "<<j<<endl;
- }
- };
- class Child : public Base
- {
- //我們覆寫帶有預設引數的函式,VA外掛給出了提醒,這兩個值都是有預設引數的
- void Print(int i /* = 1 */, int j /* = 2 */)
- {
- cout<<"In Child: "<<i<<" "<<j<<endl;
- }
- };
- int _tmain(int argc, _TCHAR* argv[])
- {
- Base* base = new Child();
- base->Print();
- system("pause");
- return 0;
- }
請按任意鍵繼續. . .
但是,如果我們不信邪,偏偏要給子類加一個不同的預設引數,結果就會大大出乎我們的意料:
- // C++Test.cpp : 定義控制檯應用程式的入口點。
- //
- #include "stdafx.h"
- #include <iostream>
- #include <string>
- usingnamespace std;
- class Base
- {
- public:
- virtualvoid Print(int i = 1, int j = 2)
- {
- cout<<"In base: "<<i<<" "<<j<<endl;
- }
- };
- class Child : public Base
- {
- public:
- //我們手動的將預設引數修改了
- void Print(int i = 3, int j = 4 )
- {
- cout<<"In Child: "<<i<<" "<<j<<endl;
- }
- };
- int _tmain(int argc, _TCHAR* argv[])
- {
- //靜態繫結
- cout<<"Static bind:"<<endl;
- Child* child = new Child();
- child->Print();
- //動態繫結
- cout<<"Dynamic bind:"<<endl;
- Base* base = new Child();
- base->Print();
- system("pause");
- return 0;
- }
Static bind:
In Child: 3 4
Dynamic bind:
In Child: 1 2
請按任意鍵繼續. . .
第一個沒有問題,子類指標呼叫子類函式,輸出的結果也是子類給出的預設引數。但是,第二個問題就大了,我們明明觸發了多型,但是,輸出的結果竟然是基類給出的那兩個預設引數的值!!!
為什麼會這樣?因為為了效率,函式的預設引數是使用靜態繫結的,換句話說,不管你有沒有多型,我只關心你用什麼指標來調,基類指標就呼叫基類的預設引數,子類指標就給出子類的預設引數。而不像我們多型那樣,會發生動態繫結,可以用基類指標呼叫子類函式。而我們在一個動態繫結的函式中使用了靜態繫結的引數,結果肯定是不對的!
所以,正如《Effective C++》中所說:“絕不重新定義繼承而來的預設引數”!