1. 程式人生 > >CC++變參函式

CC++變參函式

1.C實現變參函式

C語言中,有時需要變參函式來完成特殊的功能,比如C標準庫函式printf()和scanf()。C中提供了省略符“…”能夠幫主programmer完成變參函式的書寫。變參函式原型申明如下:

type functionname(type param1,...);

變參函式至少要有一個固定引數,省略號“…”不可省略,比如printf()的原型如下:

int printf(const char *format,...);

在標頭檔案stdarg.h中定義了三個巨集函式用於獲取指定型別的實參:

void    va_start(va_list arg,prev_param);    
type
va_arg(
va_list arg,type); void va_end(va_list arg);

va在這裡是variable argument(可變引數)的意思,那麼變參函式的實現就變得相對簡單很多。一般的變參函式處理過程:
(1)定義一個va_list變數設為va;
(2)呼叫va_start()使得va存放變參函式的變參前的一個固定引數的地址;
(3)不斷呼叫va_arg()使得va指向下一個實參;
(4)最後呼叫va_end()表示變參處理完成,將va置空。
原理就是:函式的引數在記憶體中從低地址向高地址依次存放。

看一個例子:模仿pritnf()的實現

[1]

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

void func(char *c,...) 
{        
    int i=0;  
    double result=0;  
    va_list arg;        //va_list變數  
    va_start(arg,c);    //arg指向固定引數c  
    while(c[i]!='\0') 
    {      
        if
(c[i]=='%'&&c[i+1]=='d') { printf("%d",va_arg(arg,int)); i++; } else if(c[i]=='%'&&c[i+1]=='f') { printf("%f",va_arg(arg,double)); i++; } else { putchar(c[i]); } i++; } va_end(arg); } int main() { int i=100; double j=100.0; printf("%d be equal %f\n",i,j); func("%d be equal %f\n",i,j); system("pause"); }

程式輸出:

100 be equal 100.000000
100 be equal 100.000000

C變參函式缺點[2]
(1)缺乏型別檢查,容易出現不合理的強制型別轉換。在獲取實參時,是通過給定的型別進行獲取,如果給定的型別與實際引數型別不符,則會出現型別安全性問題,容易導致獲取實參失敗。
(2)不支援自定義型別。自定義型別在程式中經常用到,比如我們要使用printf()來列印一個Student型別的物件的內容,該用什麼格式字串去指定實參型別,通過C提供的va_list,我們無法提取實參內容。

鑑於以上兩點,李健老師在其著作《編寫高質量程式碼改善C++程式的150個建議》建議儘量不要使用C風格的變參函式。

2.C++實現變參函式

為了編寫能夠處理不同數量實參的函式,C++11提供了兩種主要方法:
(1)如果所有實參型別相同,可以傳遞標準庫型別initializer_list;
(2)如果實參型別不同,可以編寫一種特殊的函式,也就是所謂的可變引數模板。

2.1initializer_list形參[3]

initializer_list是C++11引入的一種標準庫類模板,用於表示某種特定型別值的陣列。initializer_list型別定義在同名的標頭檔案中,它提供的操作有:

initializer_list<T> lst;            //預設初始化T型別的空列表。
initializer_list<T> lst{a,b,c,...}; //lst的元素是對應初始值的副本,且列表中的元素是const。
lst2(lst);      //拷貝構造一個initializer_list物件,不拷貝列表中的元素,與原始列表共享元素
lst2=lst;       //賦值,與原始列表共享元素。

lst.size();     //列表中的元素數量。
lst.begin();    //返回指向lst中首元素的指標。
lst.end();      //返回lst中尾元素下一位置的指標。

和vector與list一樣,initializer_list也是一種模板型別,定義initializer_list物件時必須指明列表中所含元素的型別。與vector和list不同之處在於initializer_list中的元素不可修改,並且拷貝構造和賦值時元素不會被拷貝。如此設計,讓initializer_list更加符合引數通過指標傳遞,而非值傳遞,提高效能。所以C++11採用了initializer_list作為變參函式的形參,下面給出一個列印錯誤的變參函式:

void error_msg(initializer\_list<string> il) 
{
    for(auto beg=il.begin();beg!=il.end())
    {
        cout<<*beg<<" ";
    }
    cout<<endl;
}

2.2可變引數模板

簡介:
目前大部分主流編譯器的最新版本均支援了C++11標準(官方名為ISO/IEC14882:2011)大部分的語法特性,其中比較難理解的新語法特性可能要屬可變引數模板(variadic template)了,GCC 4.6和Visual studio 2013都已經支援變參模板。可變引數模板就是一個接受可變數目引數的函式模板或類模板。可變數目的引數被稱為引數包(parameter packet),這個也是新引入C++的概念,可以細分為兩種引數包:
(1)模板引數包(template parameter packet),表示零個或多個模板引數。
(2)函式引數包(function parameter packet),表示零個或多個函式引數。

可變引數模板示例:
使用省略號…來指明一個模板的引數包,在模板引數列表中,class...typename...指出接下來的引數表示零個或多個型別引數;一個型別名後面跟一個省略號表示零個或多個給定型別的非型別引數。宣告一個帶有可變引數個數的模板的語法如下所示:

//1.申明可變引數的類模板
template<typename... Types> class tuple;
tuple<int, string> a;  // use it like this

//2.申明可變引數的函式模板
template<typename T,typename... Types> void foo(const T& t,const Types&... rest);
foo<int,float,double,string>(1,2.0,3.0,"lvlv");//use like this

//3.申明可變非型別引數的函式模板(可變非型別引數也可用於類模板)
template<typename T,unsigned... args> void foo(const T& t);
foo<string,1,2>("lvlv");//use like this

其中第一條示例中Types就是模板引數包,第二條示例中rest就是函式引數包,第三條示例中args就是非型別模板引數包。

引數包擴充套件:
現在我們知道parameter packet了,怎麼在程式中真正具體地去處理打包進來的“任意個數”的引數呢?也就是說可變引數模板,我們如何進行引數包的擴充套件,獲取傳入的引數包中的每一個實參呢?對於一個引數包,可以通過運算子sizeof…來獲取引數包中的引數個數,比如:

template<typename... Types> void g(Types... args)
{
    cout<<sizeof...(Types)<<endl;  //型別引數數目
    cout<<sizeof...(args)<<endl;   //函式引數數目
}

我們能夠對引數包唯一能做的事情就是對其進行擴充套件,擴充套件一個包就是將它分解為構成的元素,通過在引數包的右邊放置一個省略號…來觸發擴充套件操作,例如:

template<typename T,typename... Types> ostream& print(ostream& os,const T& t,const Types&... rest){
    os<<t<<",";
    return print(os,rest...);
}

上面的示例程式碼中,存在兩種包擴充套件操作:
(1)const Types&... rest表示模板引數包的擴充套件,為print函式生成形參列表;
(2)對print的呼叫中rest...表示函式引數包的擴充套件,為print呼叫生成實參列表。

可變引數函式例項:
可變引數函式通常以遞迴的方式來獲取引數包的每一個引數。第一步呼叫處理包中的第一個實參,然後用剩餘實參呼叫自身。最後,定義一個非可變引數的同名函式模板來終止遞迴。我們以自定義的print函式為例,實現如下:

#include <iostream>  
using namespace std;

template<typename T> ostream& print(ostream& os,const T& t) 
{
    os<<t<<endl;  //包中最後一個元素之後列印換行符
}

template<typename T,typename... Types> ostream& print(ostream& os,const T& t,const Types&... rest){
    os<<t<<",";  //列印第一個實參
    print(os,rest...);  //遞迴呼叫,列印其他實參
}

int main()
{
    print(cout,10,123.0,"lvlv",1); //例1
    print(cout,1,"lvlv0","lvlv1");
}

程式輸出:

10,123,lvlv,1
1,lvlv0,lvlv1

上面遞迴呼叫print,以例1為例,執行的過程如下:

呼叫 t rest…
print(cout,10,123.0,”lvlv”,1) 10 123.0,”lvlv”,1
print(cout,123.0,”lvlv”,1) 123.0 “lvlv”,1
print(cout,”lvlv”,1) “lvlv” 1
print(cout,1),呼叫非變參版本的print 1

前三個呼叫只能與可變引數版本的print匹配,非變參版本是不可行的,因為這三個呼叫要傳遞兩個以上實參,非可變引數的print只接受兩個實參。對於最後一次遞迴呼叫print(cout,1),兩個版本的print都可以,因為這個呼叫傳遞兩個實參,第一個實參的型別為ostream&,另一個是const T&引數。但是由於非可變引數模板比可變引數模板更加特例化,因此編譯器選擇非可變引數版本。

[1]編寫高質量程式碼改善C++程式的150個建議.李健.2012:34-35
[2]c /c++變參函式
[3]Stanley B. Lippman著,王剛 楊巨峰譯.C++ Primer中文版第五版.2013:197-199

相關推薦

CC++函式

1.C實現變參函式 C語言中,有時需要變參函式來完成特殊的功能,比如C標準庫函式printf()和scanf()。C中提供了省略符“…”能夠幫主programmer完成變參函式的書寫。變參函式原型申明如下: type functionname(type

C函式其實不難

//這裡給出的是printf的簡易實現(非原創)//按照va_list,va_start,va_copy,va_arg,va_end的順序使用//注意:1.void va_start( va_list ap, parmN );第二個引數是你定義的引數列表的最後一個固定引數。2.T

如何使用函式

對於C語言的初學者來說printf函式是經常被使用的。但是不知道大家在初學的時候有沒有注意到printf這個函式可以定義成這樣: printf("%d",a); //有兩個形參 也可以是這樣: printf("%d %d",a,b); //有三個形參

C語言函式巨集定義分析

)bnd一般取3(32位cpu)或7(64位cpu) 。以32位cpu為例:~(bnd) = 0xfffffffc,該值相當於一個掩碼,使任何數與它相與後的結果都是4的倍數,這也正好吻合32位cpu入棧時4位元組對齊的特點;(sizeof (X)) + (bnd) = sizeof(X) + 3,保證了不

(不定)的巨集函式

1.可變的巨集是具有不定引數的巨集。這些引數用...代表,被儲存到__VA_ARGS__中。他會在內部進行擴充套件。 #defien err(...) fprintf(stderr, __AV_

C/C++傳遞回撥函式的使用

#include <stdio.h> int (*fp)(int a,int b); int add(int a,int b) { return (a+b); } int sub(int a,int b) { return (a-b); } int

標準函式的重新封裝,如printf

寫一個函式封裝printf用作trace 方法一: #include <stdio.h> #include <stdarg.h> void my_trace(const char *cmd, ...) { printf("%s %s

014_go語言中的函數

rgs body code 數量 使用 type類 pan log num 代碼演示 package main import "fmt" func sum(nums ...int) { fmt.Print(nums, " ") totol := 0

c語言解決函數數問題 va_list

res turn pri 定義 etl format) 似的 c語言 mon 前言:看到sprintf,swprintf之類的可變參數格式化函數,是否想過我們能寫一個自定義的類似的函數嗎?答案是很定的,下面來介紹一種方法,用va_list,va_start, va_end來

addEventListener繫結帶函式

因為以前一直使用的jquery,然後剛剛在使用原生dom繫結帶參的時候出現了一些問題。 初始程式碼片段 <button class="addEvent">繫結事件</button> <script> const addEvent =

函式的應用與限制

無參函式的應用與限制 在 C90 標準中,你可以宣告一個無引數資訊的函式。一個示例如下: void no_arg_func(); 使用空的小括號來表示沒有引數資訊,編譯器無法獲取到引數資訊,也就不會進行引數檢查,因此你可以傳遞任意數量的引數。下面是一個具體的應用。 #i

c語言的使用 可變引數巨集 標準預定義巨集

gcc的預處理提供的可變引數巨集定義真是好用: #ifdef DEBUG #define dbgprint(format,args...) / fprintf(stderr, format, ##args) #else #define dbgprint(fo

列表陣列函式array()對資料型別產生的影響

在進行一個運算時發現list型別不能直接進行矩陣運算,需要用array()函式轉換為陣列型別,然後進行運算。但是發現結果有問題,本來是一列float,求和之後變成了一個大長串的數字,沒有得到想要的結果。 hs300stock_data=pd.DataF

C++11 模板(variadic templates)

Variadic Template是C++11的一個很重要的特性; 變體現在兩個方面: (1)引數個數:利用引數個數逐一遞減的特性,實現遞迴呼叫; (2)引數型別:引數個數逐一遞減導致引數型別也逐一遞減; 兩個注意點 (1)遞迴呼叫 (2)遞迴終止:使用過載的辦法終止遞迴呼叫;

定時器呼叫含引數的函式函式

通常我們使用定時器來實現某種功能的時候,如下。 function numAdd(num){ num++; console.log(num); } setInterval(n

JavaScript-函式的呼叫,無函式和有函式,引數的的傳遞

javascript中的函式分為無參函式和有參函式,無參函式就是函式裡面什麼都別寫,有參函式有變數在裡面。 <!DOCTYPE html> <html> <he

C函式不寫return以及呼叫無函式時傳參會出現什麼結果

1. 問題描述   偶然間重新拿起了三年前的C語言,遇到了以前沒有遇到過的問題: 1. C語言中普通函式聲明瞭返回型別但是不用return返回結果,也能夠編譯通過,如下: #include <stdio.h> int test1(int a

Python有函式和無函式例項

5.2.4 函式、生成器和類 還是從幾個例子看起: def say_hello(): print('Hello!') def greetings(x='Good morning!'): print(x) say_hello() # He

const引用做形---函式引數的傳遞,並不都是複製

     使用引用做形參,可以直接訪問實參物件,並改變實參內容,而不是將實參複製給形參,所以在大資料傳遞時, 用引用做形參可以提高效率。 void f(int& x)//引用做形參 { x=7; } int main() { int y=0;

C語言中: va_list 解決問題

VA_LIST 是在C語言中解決變參問題的一組巨集,所在標頭檔案:#include <stdarg.h>,用於獲取不確定個數的引數 ——摘自百度百科 va_list 是一個字元指標,在程