天天看點

C++那些細節--函數的預設參數一.形參&實參二.簡單使用三.注意事項四.預設參數和函數重載的沖突五.覆寫函數時不要更換預設參數

關于C++函數預設參數的問題,來一個整理。

一.形參&實參

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

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

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

二.簡單使用

C++函數支援預設參數,這是一個很友善的特性。我們在函數聲明或者定義的時候,給函數的參數設定一個預設值,當調用時如果不給參數或者給出一部分參數,那麼就使用函數設定的預設參數值。先看一個例子:

// C++Test.cpp : 定義控制台應用程式的入口點。
//

#include "stdafx.h"
#include <iostream>
using namespace 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>
using namespace 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>
using namespace std;

class Base
{
public:
	virtual void 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;
}
           

結果: In Child: 1 2

請按任意鍵繼續. . .

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

// C++Test.cpp : 定義控制台應用程式的入口點。
//

#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;

class Base
{
public:
	virtual void 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++》中所說:“絕不重新定義繼承而來的預設參數”!