C++中的默認參數規則


C++中的默認參數規則

C++的默認參數規則其實是一個非常容易掉坑的規則,尤其是當一個函數擁有多個聲明的時候,每個聲明的默認參數可以各不相同,在調用時又可能與每個聲明都不同;這篇博客稍微列舉一下C++中的默認參數規則。

前置

在開始之前,我們先來復習一下,函數可以有多個聲明,定義 是有函數體的聲明。默認參數則是函數聲明中使用特殊語法(decl-specifier-seq declarator = initializer)為某個參數提供的默認值。這種語法就像在參數列表中寫參數對象的拷貝初始化一樣。

void foo(int a, int b = 0);
void func(int a = 1, int b = 2, int c = 3);

默認參數語法存在的目的,是為用戶在函數調用時,可以不提供尾隨參數。

func();
func(1);
func(1,2);

非常容易理解,由於C++目前還沒有像python那樣指定參數的語法,因而需要提供默認值的參數必須放到參數列表的后面。實質上,這相當於編譯器替用戶向函數中傳遞參數。

那么,這里就有這么幾個問題,什么地方可以有默認參數?哪些東西可以作為默認參數?當函數有多個聲明時,默認參數如何工作?

什么地方可以有默認參數?

到我開始寫這篇文章為止,C++允許普通的函數聲明(包括類成員函數)、lambda表達式中使用默認參數,而不允許函數指針、函數引用以及typedef聲明中出現默認參數。具體可見這里。所以,在需要管理各種包裝計算函數的對象的場景中(algorithm factory),由於大部分手法使用函數指針進行,保存默認參數需要使用額外的空間並在運行時完成。

這里可以稍微留個問題,std::function支持默認參數嗎?如果不,為什么?

什么可以作為默認參數?

從文法上說,所有能做initializer的東西都能作為默認參數,但問題在於,有些東西出現在initializer中是不被允許的,這個范圍還挺廣。

  1. 局部變量不能在默認參數中
  2. this指針不能在默認參數中
  3. 其他的參數不能在默認參數中
  4. 非靜態成員不能在默認參數中
  5. 有捕獲內容的lambda表達式不能在默認參數中
int main(int argc = 0,char** argc = {0});  // ok
int foo(int a1, int a2, int a3 = a1 + a2); // bad
int func(int a1, int a2, int a3 = 1 + 2); // ok
void helper()
{
    int n = 1;
    int func(int a1 = n);//bad
}
//C is a class with copy constructor
C::foo(C p = *this); // bad

class C
{
    static int s;
    int a;
    //void func(int n = a);//bad
    void func(int n = s);//OK
}

//after C++11
int globalV;
void defaultFuncs(int a1 = ([]()->int {return 0; })(), int a2 = 1, int a3 = 3 + 2);//ok
//void defaultFuncs(int a1 = ([]()->int {return globalV; })(), int a2 = 1, int a3 = 2);//bad

並且,由於默認參數實質上就是編譯器替用戶填參數,而函數調用時會發生argument到parameter的拷貝初始化,因而這個initializer必須要能夠滿足到相應parameter的拷貝初始化。

除此之外,其它的東西都可以做默認參數。

多個聲明與默認參數

如果每個函數都只有一個聲明兼定義,那么事情會簡單很多,但是,在C++中,一個函數可以有多個聲明,但只能有一個定義,這和名稱查找規則共同協作,構成了C++的分離編譯特性。隨之而來的,默認參數在多個聲明之間有很有趣的工作特性。

多個聲明之間的默認參數組合

第一個特性,就是不同聲明之間的默認參數是能組合的。

void multi(int a1, int a2, int a3)
{
	std::cout << a1 << a2 << a3 << '\n';
}

void multi(int a1,int a2, int a3 = 3);

void multi(int a1, int a2 = 2, int a3);

void multi(int a1 = 1, int a2, int a3);

int main()
{
    multi();//ok ,call multi(1,2,3);
}

這是一個很隱蔽的特性,隱蔽到筆者的VS2017intelliScene會對它報錯,然而編譯還是能通過。事實上,標准中有這么一句規定:

所有的有默認參數的形參后面,所有的形參都必須在這個聲明或者在先前的聲明提供默認參數,或者是參數包。

在函數調用點,實際上的默認參數是函數所有可見聲明的默認參數的聯合。但是相應的,在這個可見集合中,不能有對於同一個形參重復的默認參數聲明,即使是同一個值也不行。

int foo(int,int);
int foo(int a1, int a2 = 0);
int foo(int a1, int a2 = 0);//bad

內部作用域

簡單而言,內部作用域中可以重新聲明一個函數,並且可以忽視外部聲明的默認參數。在這個內部作用域中的函數調用,其默認參數集合也是這個這個作用域中所有聲明的默認參數聯合。當然,這僅僅是可見聲明這個概念,也就是名稱查找的小游戲而已。

同理,如果你外部作用域與內部作用域均有默認參數定義,那么using會同時把默認參數導入進來。

其它的小規則

默認參數還有一些其它的小規則。

在類外定義的函數,可以將其默認參數與聲明組合,但不能將成員函數變成構造函數。

虛函數的默認參數由靜態類型決定。

除了調用運算符以外,其它的運算符都不能有默認參數。

對於在不同翻譯單元內定義的內聯函數,那么在每個翻譯單元末尾,默認參數集都需要相同。

友元函數聲明如果有默認參數,那么這個聲明必須是定義並且在這個翻譯單元中不會再有別的聲明


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM