在c++程式設計中,有時我們需要編寫一些在源**編寫階段無法確定引數個數,有時甚至無法確定引數型別的函式。
例如,乙個求和函式。可以通過過載實現若干個數的和。
int
sum(int i1, int i2);
intsum(int i1, int i2, int i3);
...//還可以過載更多類似函式
double
sum(double d1, double d2);
double
sum(double d1, double d2, double d3);
...//還可以過載更多類似函式
以上**通過過載機制來解決變參問題。但很快我們就會發現這種方法存在的問題:必須確保所有可能的實參列表都有對應的過載宣告和定義,如果上述方法如果參與運算的引數個數可能從2——20個不等,那麼我們就需要過載19次同乙個函式。
我們需要的是這樣一類函式:它們可以在執行時取任意的實參個數並根據實參的個數自動處理不同實參的情形,或者至少可以在執行時指定任意的實參個數。
在c++中實現乙個變參函式的方法有三種:第一種方法,將函式形參宣告為c++11新標準中的initializer_list標準庫型別;第二種方法繼承自c語言,形參宣告為省略符,函式實現時用引數列表巨集訪問引數;最後一種方法利用c++泛型特性,宣告乙個可變引數模板來實現。
1. 可變引數巨集
實現步驟如下:
1. 函式原型中使用省略號;
2. 函式定義中建立乙個va_list變數;
3. 初始化va_list變數;
4. 訪問引數列表;
5. 完成清理工作;
上述步驟的實現需要使用到四個巨集:va_list、va_start(va_list, arg)、va_arg(va_list, type)、va_end(va_list)這些巨集在標頭檔案stdarg.h中宣告定義。因此使用時需要包含該標頭檔案。
以下**使用可變引數巨集實現乙個函式sum,該函式接受任意個數的整形實參,返回這些實參的和。(忽略可能存在的整形溢位)
/* --sum.cpp-- 可變引數巨集實現求任意個整形值得和 */
#include
intsum(int
count, ...); //原型中使用省略號
intsum(int
count, ...)
使用這種方法需要注意一下幾點:
1. 函式原型中,省略號必須在引數列表的末尾:也就是說,在函式原型中引數列表省略號的右邊不能再出現確定引數;
2. 執行時,函式必須能夠根據已有資訊(既有約定,或確定實參)確定可變引數的具體個數與型別:函式定義需要知道可變引數的具體型別、個數,這些資訊是在執行時確定的,那麼顯然應該由實參來確定。在上面的例子中count傳遞了可變引數的個數,而引數型別則是既有約定(整形);
3. 使用完成時需要用va_end()做清理工作,可變引數巨集可能使用了動態分配的記憶體,忘記執行清理操作有可能導致記憶體洩漏等問題;
4. 可變引數巨集只能實現順序訪問可變引數,無法後退訪問,但是可以在清理操作完成後重新使用va_start初始化va_list變數,重新遍歷形參表;
5. 該方法是極不安全的,巨集本身無法提供任何安全性保證,他總是按照既定**「自作多情」的認為實參就應該是那麼多,即使實參並不是那麼多。這就要求所有安全性必須由程式設計師來保證。例如,在以上的示例**中,如果呼叫時指定count為10,但實際上只給出9個可變形參,那麼函式還是會讀取10個引數,顯然第十次讀取是多餘的,多餘的操作一般不會有什麼好結果,當然如果實參過多,多餘的實參也不會被讀取而是被忽略。
使用這種方法的乙個例項是printf()函式。printf()函式通過分析第乙個字串引數中的佔位符個數來確定形參的個數;通過佔位符的不同來確定引數型別(%d表示int型別、%s表示char *);它也有上述提到的安全問題,如果不小心少提供了個實參,那麼越界訪問就會發生。
2. initializer_list標準庫型別
實現步驟如下:
1. 函式原型中使用例項化initializer_list模板代表可變引數列表;
2. 使用迭代器訪問initializer_list中的引數;
3. 傳入實參寫在{}之內。
以上步驟中使用到initializer_list。這是c++11新標準中引入的乙個標準庫型別,與vector等容器一樣initializer_list也支援begin()和end()操作,返回指向首元素的迭代器和尾後迭代器。initializer_list在同名頭檔案中宣告,其實現由編譯器支援。
以下**使用initializer_list實現函式sum。(忽略可能存在的整形溢位)
/* --sum.cpp-- 利用initializer_list模板實現求人乙個整形值得和 */
#include
intsum(initializer_list il); //函式原型用int例項化initializer_list作為形參
intsum(inttializer_list il)
使用這種方法需要注意一下幾點:
1. initializer_list在c++11中才被引入,這意味著在編譯時可能需要加上這個選項-std=c++11才能成功編譯。上述**中的auto關鍵字也是c++11的一部分;
2. 引數必須放在一組『{}』(大括號)內,編譯器通過大括號來將這組引數轉化為initializer_list.大括號的的一組實參與initializer_list形參對應;
3. 函式原型initializer_list與普通形參無異。這表明形參列表中可以包含其他型別引數且位置不限,以下函式原型是正確的:
void func(char c, initializer_list il, double d);
4. 同乙個initializer_list中的引數具有相同的型別。本質上來說initializer_list是乙個編譯器支援的容器類模板,同其他容器一樣,容器中的元素具有相同的型別。
使用這種方法的乙個例項是c++11中vector的列表初始化建構函式。
3. 可變引數模板
在介紹這種方法之前需要先介紹兩個並不常用的概念:模板引數包和函式引數包。
模板引數包是零個或多個型別引數的集合。模板引數列表中,class…或typename…表明其後的型別引數表示乙個模板引數包;
函式引數包是零個或多個非型別引數的集合。函式形參列表中型別名加省略號表明其後的引數表示乙個函式引數包;另外,型別為模板引數包的函式形參是乙個函式引數包。
以下引用參考書目2中的示例**來直觀展現這兩個概念:
//args是乙個模板引數包;rest是乙個函式引數包
//args表示零個或多個模板型別引數
//rest表示零個或多個函式引數
templatet, typename... args>
void foo(const t &t, const args&... rest);
與sizeof()運算子類似,sizeof…()運算子用於引數包。sizeof…()將返回引數包中引數個數。
利用可變引數模板實現可變引數函式的步驟如下:
1. 編寫含有模板引數包和函式引數包的模板函式;
2. 函式定義遞迴呼叫自己,每一步遞迴引數包中引數減一;
3. 編寫處理邊界情況(引數包含有零個引數)的模板。
以下引用參考書目2中示例**:
//用來終止遞迴並答應最後乙個元素的函式
//此函式必須在可變引數版本的print定義之前宣告
template
std::ostream &print(std::ostream &os, const t &t)
//包中除最後乙個元素之外的其他元素都會呼叫這個版本的pirnt
template
std::ostream &print(std::ostream &os, const t &t, cosnt args &... rest)
這種實現方式的根本原理實際上與最初提到的過載是一致的。通過定義模板,讓編譯器根據實參型別自動生成對應的過載函式。
以上提到的三種方法都可以實現變參函式。但三種方法都有其各自的有點和侷限性,在選擇時可以從以下幾個方面考慮:
1.若非必要,不要使用可變引數函式。應該首先考慮函式過載等其他方法。
2.除非需要相容c語言編譯器,否則不要使用可變引數巨集。應為這種方法最不安全;尤其是當引數為物件時這種方法易產生各種問題。畢竟這些巨集是為c語言設計的,c語言中沒有物件。
3. 如果引數型別相同且c++11可用,則通過宣告形參為initializer_list往往是最簡單、最有效的辦法。
4. 變參模板看似最為強大。引數的型別可以不同、比可變引數巨集更加安全並且可以自動推斷引數型別和引數個數。但考慮到模板會為每乙個不同的例項生成**,如果函式的例項過多可能會使**體積增大。另外,依靠遞迴使得功能具有侷限性,並且效率也會受到影響。
c 中可變引數
在c 程式設計中,有時我們需要編寫一些在源 編寫階段無法確定引數個數,有時甚至無法確定引數型別的函式。例如,乙個求和函式。可以通過過載實現若干個數的和。int sum int i1,int i2 intsum int i1,int i2,int i3 還可以過載更多類似函式 double sum d...
c 中可變引數
在c 程式設計中,有時我們需要編寫一些在源 編寫階段無法確定引數個數,有時甚至無法確定引數型別的函式。例如,乙個求和函式。可以通過過載實現若干個數的和。int sum int i1,int i2 intsum int i1,int i2,int i3 還可以過載更多類似函式 double sum d...
c 中可變引數
在c 程式設計中,有時我們需要編寫一些在源 編寫階段無法確定引數個數,有時甚至無法確定引數型別的函式。例如,乙個求和函式。可以通過過載實現若干個數的和。int sum int i1,int i2 intsum int i1,int i2,int i3 還可以過載更多類似函式 double sum d...