c/c++支持可變參數(shù)的函數(shù)

字號(hào):

一、為什么要使用可變參數(shù)的函數(shù)?
    一般我們編程的時(shí)候,函數(shù)中形式參數(shù)的數(shù)目通常是確定的,在調(diào)用時(shí)要依次給出與形式參數(shù)對(duì)應(yīng)的所有實(shí)際參數(shù)。但在某些情況下希望函數(shù)的參數(shù)個(gè)數(shù)可以根據(jù)需要確定,因此c語言引入可變參數(shù)函數(shù)。這也是c功能強(qiáng)大的一個(gè)方面,其它某些語言,比如fortran就沒有這個(gè)功能。
    典型的可變參數(shù)函數(shù)的例子有大家熟悉的printf()、scanf()等。
    二、c/c++如何實(shí)現(xiàn)可變參數(shù)的函數(shù)?
    為了支持可變參數(shù)函數(shù),C語言引入新的調(diào)用協(xié)議, 即C語言調(diào)用約定 __cdecl . 采用C/C++語言編程的時(shí)候,默認(rèn)使用這個(gè)調(diào)用約定。如果要采用其它調(diào)用約定,必須添加其它關(guān)鍵字聲明,例如WIN32 API使用PASCAL調(diào)用約定,函數(shù)名字之前必須加__stdcall關(guān)鍵字。
    采用C調(diào)用約定時(shí),函數(shù)的參數(shù)是從右到左入棧,個(gè)數(shù)可變。由于函數(shù)體不能預(yù)先知道傳進(jìn)來的參數(shù)個(gè)數(shù),因此采用本約定時(shí)必須由函數(shù)調(diào)用者負(fù)責(zé)堆棧清理。舉個(gè)例子:
    //C調(diào)用約定函數(shù)
    int __cdecl Add(int a, int b)
    {
    return (a + b);
    }
    函數(shù)調(diào)用:
    Add(1, 2);
    //匯編代碼是:
    push     2       ;參數(shù)b入棧
    push     1       ;參數(shù)a入棧
    call     @Add    ;調(diào)用函數(shù)。其實(shí)還有編譯器用于定位函數(shù)的表達(dá)式這里把它省略了
    add    esp,8     ;調(diào)用者負(fù)責(zé)清棧
    如果調(diào)用函數(shù)的時(shí)候使用的調(diào)用協(xié)議和函數(shù)原型中聲明的不一致,就會(huì)導(dǎo)致棧錯(cuò)誤,這是另外一個(gè)話題,這里不再細(xì)說。
    另外c/c++編譯器采用宏的形式支持可變參數(shù)函數(shù)。這些宏包括va_start、va_arg和va_end等。之所以這么做,是為了增加程序的可移植性。屏蔽不同的硬件平臺(tái)造成的差異。
    支持可變參數(shù)函數(shù)的所有宏都定義在stdarg.h 和 varargs.h中。例如標(biāo)準(zhǔn)ANSI形式下,這些宏的定義是:
    typedef char * va_list; //字符串指針
    #define _INTSIZEOF(n)  ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
    #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
    #define va_arg(ap,t)   ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
    #define va_end(ap)   ( ap = (va_list)0 )
    使用宏_INTSIZEOF是為了按照整數(shù)字節(jié)對(duì)齊指針,因?yàn)閏調(diào)用協(xié)議下面,參數(shù)入棧都是整數(shù)字節(jié)(指針或者值)。
    三、如何定義這類的函數(shù)。
    可變參數(shù)函數(shù)在不同的系統(tǒng)下,采用不同的形式定義。
    1、用ANSI標(biāo)準(zhǔn)形式時(shí),參數(shù)個(gè)數(shù)可變的函數(shù)的原型聲明是:
    type funcname(type para1, type para2, ……);
    關(guān)于這個(gè)定義,有三點(diǎn)需要說明:
    一般來說,這種形式至少需要一個(gè)普通的形式參數(shù),可變參數(shù)就是通過三個(gè)'.'來定義的。所以"……"不表示省略,而是函數(shù)原型的一部分。type是函數(shù)返回值和形式參數(shù)的類型。
    例如:
    int MyPrintf(char const* fmt, ……);
    但是,我們也可以這樣定義函數(shù):
    void MyFunc(……);
    但是,這樣的話,我們就無法使用函數(shù)的參數(shù)了,因?yàn)闊o法通過上面所講的宏來提取每個(gè)參數(shù)。所以除非你的函數(shù)代碼中的確沒有用到參數(shù)表中的任何參數(shù),否則必須在參數(shù)表中使用至少一個(gè)普通參數(shù)。
    注意,可變參數(shù)只能位于函數(shù)參數(shù)表的最后。不能這樣:
    void MyFunc(……, int i);
    2、采用與UNIX 兼容系統(tǒng)下的聲明方式時(shí),參數(shù)個(gè)數(shù)可變的函數(shù)原型是:
    type funcname(va_alist);
    但是要求函數(shù)實(shí)現(xiàn)的時(shí)候,函數(shù)名字后面必須加上va_dcl.例如:
    #i nclude
    int average( va_list );
    void main( void )
    {
    。。。//代碼
    }
    /* UNIX兼容形式*/
    int average( va_alist )
    va_dcl
    {
    。。。//代碼
    }
    這種形式不需要提供任何普通的形式參數(shù)。type是函數(shù)返回值的類型。va_dcl是對(duì)函數(shù)原型聲明中參數(shù)va_alist的詳細(xì)聲明,實(shí)際是一個(gè)宏定義。根據(jù)平臺(tái)的不同,va_dcl的定義稍有不同。
    在varargs.h中,va_dcl的定義后面已經(jīng)包括了一個(gè)分號(hào)。因此函數(shù)實(shí)現(xiàn)的時(shí)候,va_dcl后不再需要加上分號(hào)了。
    3、采用頭文件stdarg.h編寫的程序是符合ANSI標(biāo)準(zhǔn)的,可以在各種操作系統(tǒng)和硬件上運(yùn)行;而采用頭文件varargs.h的方式僅僅是為了與以前的程序兼容,兩種方式的基本原理是一致的,只是在語法形式上有一些細(xì)微的區(qū)別。 所以一般編程的時(shí)候使用stdarg.h.下面的所有例子代碼都采用ANSI標(biāo)準(zhǔn)格式。
    四、可變參數(shù)函數(shù)的基本使用方法
    下面通過若干例子,說明如何實(shí)現(xiàn)可變參數(shù)函數(shù)的定義和調(diào)用。
    //================================ 例子程序1 ===============
    #i nclude < stdio.h >
    #i nclude < string.h >
    #i nclude < stdarg.h >
    /* 函數(shù)原型聲明,至少需要一個(gè)確定的參數(shù),注意括號(hào)內(nèi)的省略號(hào) */
    int demo( char *, ... );
    void main( void )
    {
    demo( "DEMO", "This", "is", "a", "demo!", "\0");
    }
    int demo( char *msg, ... )
    {
    va_list argp; /* 定義保存函數(shù)參數(shù)的結(jié)構(gòu) */
    int argno = 0; /* 紀(jì)錄參數(shù)個(gè)數(shù) */
    char *para; /* 存放取出的字符串參數(shù) */
    // 使用宏va_start, 使argp指向傳入的第一個(gè)可選參數(shù),
    // 注意 msg是參數(shù)表中最后一個(gè)確定的參數(shù),并非參數(shù)表中第一個(gè)參數(shù)
    va_start( argp, msg );
    while (1)
    {
    //取出當(dāng)前的參數(shù),類型為char *
    //如果不給出正確的類型,將得到錯(cuò)誤的參數(shù)
    para = va_arg( argp, char *);
    if ( strcmp( para, "\0") == 0 ) /* 采用空串指示參數(shù)輸入結(jié)束 */
    break;
    printf( "參數(shù) #%d 是: %s\n", argno, para);
    argno++;
    }
    va_end( argp ); /* 將argp置為NULL */
    return 0;
    }