C,C++中使用可变参数
可變參數(shù)即表示參數(shù)個(gè)數(shù)可以變化,可多可少,也表示參數(shù)的類型也可以變化,可以是int,double還可以是char*,類,結(jié)構(gòu)體等等。可變參數(shù)是實(shí)現(xiàn)printf(),sprintf()等函數(shù)的關(guān)鍵之處,也可以用可變參數(shù)來對任意數(shù)量的數(shù)據(jù)進(jìn)行求和,求平均值帶來方便(不然就用數(shù)組或每種寫個(gè)重載)。在C#中有專門的關(guān)鍵字parame,但在C,C++并沒有類似的語法,不過幸好提供這方面的處理函數(shù),本文將重點(diǎn)介紹如何使用這些函數(shù)。
?
第一步?可變參數(shù)表示
用三個(gè)點(diǎn)…來表示,查看printf()函數(shù)和scanf()函數(shù)的聲明:
int?printf(const?char?*, ...);
int?scanf(const?char?*, ...);
這三個(gè)點(diǎn)用在宏中就是變參宏(Variadic Macros),默認(rèn)名稱為__VA_ARGS__。如:
#define?WriteLine(...) {?printf(__VA_ARGS__);?putchar('\n');}
再WriteLine("MoreWindows");
考慮下printf()的返回值是表示輸出的字節(jié)數(shù)。將上面宏改成:
#define?WriteLine?(...)?printf(__VA_ARGS__) + (putchar('\n') !=?EOF?? 1: 0);
這樣就可以得到WriteLine宏的返回值了,它將返回輸出的字節(jié)數(shù),包括最后的’\n’。如下例所示i和j都將輸出12。
???????int?i?= WriteLine("MoreWindows");
???????WriteLine("%d",?i);
???????int j =?printf("%s\n",?"MoreWindows");
???????WriteLine("%d", j);
?
第二步?如何處理va_list類型
函數(shù)內(nèi)部對可變參數(shù)都用va_list及與它相關(guān)的三個(gè)宏來處理,這是實(shí)現(xiàn)變參參數(shù)的關(guān)鍵之處。
在<stdarg.h>中可以找到va_list的定義:
typedef char *??va_list;
再介紹與它關(guān)系密切的三個(gè)宏要介紹下:va_start(),va_end()和va_arg()。
同樣在<stdarg.h>中可以找到這三個(gè)宏的定義:
#define?va_start(ap,v)??( ap = (va_list)&v?+?_INTSIZEOF(v) )
#define?va_end(ap)??????( ap = (va_list)0 )
#define?va_arg(ap,t)????( *(t?*)((ap +=?_INTSIZEOF(t)) -?_INTSIZEOF(t)) )
其中用到的_INTSIZEOF宏定義如下:
#define?_INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
來分析這四個(gè)宏:
va_end(ap)這個(gè)最簡單,就是將指針置成NULL。
va_start(ap,v)中ap = (va_list)&v?+?_INTSIZEOF(v)先是取v的地址,再加上_INTSIZEOF(v)。_INTSIZEOF(v)就有點(diǎn)小復(fù)雜了。( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )全是位操作,看起來有點(diǎn)麻煩,其實(shí)不然,非常簡單的,就是取整到sizeof(int)。比如sizeof(int)為4,1,2,3,4就取4,5,6,7,8就取8。對x向n取整用C語言的算術(shù)表達(dá)就是((x+n-1)/n)*n,當(dāng)n為2的冪時(shí)可以將最后二步運(yùn)算換成位操作——將最低?n - 1個(gè)二進(jìn)制位清?0就可以了。
va_arg(ap,t)就是從ap中取出類型為t的數(shù)據(jù),并將指針相應(yīng)后移。如va_arg(ap,?int)就表示取出一個(gè)int數(shù)據(jù)并將指針向移四個(gè)字節(jié)。
因此在函數(shù)中先用va_start()得到變參的起始地址,再用va_arg()一個(gè)一個(gè)取值,最后再用va_end()收尾就可以解析可變參數(shù)了。
?
第三步?vfprintf()函數(shù)和vsprintf()函數(shù)
vfprintf()這個(gè)函數(shù)很重要,光從名字上看就知道它與經(jīng)常使用的printf()函數(shù)有很大的關(guān)聯(lián)。它有多個(gè)重載版本,這里講解最常用的一種:
函數(shù)原型
int vfprintf(
???FILE *stream,
???const char *format,
???va_list argptr
);
第一個(gè)參數(shù)為一個(gè)FILE指針。FILE結(jié)構(gòu)在C語言的讀寫文件必不可少。要對屏幕輸出傳入stdout。
第二個(gè)參數(shù)指定輸出的格式。
第三個(gè)參數(shù)是va_list類型,這個(gè)少見,但其實(shí)就是一個(gè)char*表示可變參參數(shù)的起始地址。
返回值:成功返回輸出的字節(jié)數(shù)(不包括最后的’\0’),失敗返回-1。
vsprintf()與上面函數(shù)類似,就只列出函數(shù)原型了:
int vsprintf(
???char *buffer,
???const char *format,
???va_list argptr
);
還有一個(gè)int _vscprintf(const char *format, va_list argptr );可以用來計(jì)算vsprintf()函數(shù)中的buffer字符串要多少字節(jié)的空間。
?
代碼范例
?下面就給出了自己實(shí)現(xiàn)的printf()函數(shù)(注1)與WriteLine()函數(shù)
int Printf(char *pszFormat, ...)?
{
???????va_list???pArgList;
??????
???????va_start(pArgList,?pszFormat);
???????int?nByteWrite?=?vfprintf(stdout,?pszFormat,?pArgList);
???????va_end(pArgList);
?
???????return?nByteWrite;
}
?
int WriteLine(char *pszFormat, ...)
{
???????va_list???pArgList;
??????
???????va_start(pArgList,?pszFormat);
???????int?nByteWrite?=?vfprintf(stdout,?pszFormat,?pArgList);
???????if (nByteWrite?!= -1)
??????????????putchar('\n'); //注2
???????va_end(pArgList);
??????
???????return (nByteWrite?== -1 ? -1 :?nByteWrite?+ 1);
}
調(diào)用與printf()函數(shù)相同。
再給出一個(gè)用可變參數(shù)來求和,遺憾的在C,C++中無法確定傳入的可變參數(shù)的個(gè)數(shù)(printf()中是通過掃描'%'個(gè)數(shù)來確實(shí)參數(shù)的個(gè)數(shù)的),因此要么就要指定個(gè)數(shù),要么在參數(shù)的最后要設(shè)置哨兵數(shù)值:
設(shè)置哨兵數(shù)值:
const int?GUARDNUMBER?= 0;?//哨兵標(biāo)識
//變參參數(shù)的個(gè)數(shù)無法確定,在printf()中是通過掃描'%'個(gè)數(shù),在這通過設(shè)置哨兵標(biāo)識來確定變參參數(shù)的終止
int MySum(int?i, ...)
{
???????int?sum?=?i;
???????va_list?argptr;
??????
???????va_start(argptr,?i);
???????while ((i?=?va_arg(argptr, int)) !=?GUARDNUMBER)
??????????????sum?+=?i;
???????va_end(argptr);
??????
???????return?sum;
}
可以這樣的調(diào)用:???printf("%d\n", MySum(1, 3, 5, 7, 9, 0));
但不可以直接傳入一個(gè)0:???printf("%d\n", MySum(0)); //error
指定個(gè)數(shù):
int MySum(int?nCount, ...)
{
???????if (nCount?<= 0)
??????????????return 0;
?
???????int?sum?= 0;
???????va_list?argptr;
??????
???????va_start(argptr,?nCount);
???????for (int?i?= 0;?i?<?nCount;?i++)
??????????????sum?+=?va_arg(argptr, int);
???????va_end(argptr);
??????
???????return?sum;
}
調(diào)用時(shí)第一個(gè)參數(shù)表示后面參數(shù)的個(gè)數(shù)如:
???????printf("%d\n", MySum(5, 1, 3, 5, 7, 9));
???????printf("%d\n", MySum(0));
代碼所用的頭文件:
#include?<stdarg.h>
#include?<stdio.h>
?
可變參數(shù)的使用方法遠(yuǎn)遠(yuǎn)不止上述幾種,不過在C,C++中使用可變參數(shù)時(shí)要小心,在使用printf()等函數(shù)時(shí)傳入的參數(shù)個(gè)數(shù)一定不能比前面的格式化字符串中的’%’符號個(gè)數(shù)少,否則會產(chǎn)生訪問越界,運(yùn)氣不好的話還會導(dǎo)致程序崩潰。
?
可變參數(shù)的原形理涉及到調(diào)用函數(shù)時(shí)參數(shù)的入棧問題,這個(gè)下次再開一篇進(jìn)行專門的探討。
?
?
?
注1.????網(wǎng)上有不用vfprintf()自己解析參數(shù)來實(shí)現(xiàn)printf()的,但很少能將功能做到與printf()相近(實(shí)際上能完全熟悉printf()的人已經(jīng)就不多,不信的話可以先看看《C陷阱與缺陷》了解printf()很多不太常用的參數(shù),再去Microsoft Visual Studio\VC98\CRT\SRC中查看OUTPUT.C對printf()的實(shí)現(xiàn))。
注2.????如果輸出單個(gè)字符?putchar(ch)會比printf(“%c”, ch)效率高的多。在字符串不長的情況下,多次調(diào)用putchar()也會比調(diào)用printf(“%s\n”, szStr);的效率高。在函數(shù)大量調(diào)用時(shí)非常明顯。
總結(jié)
以上是生活随笔為你收集整理的C,C++中使用可变参数的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 复制SQLSERVER数据库文件
- 下一篇: C++使用SQLite步骤及示例