C/C++中使用可變參數

原文地址:http://blog.csdn.net/morewindows/article/details/6707662windows

可變參數即表示參數個數能夠變化,可多可少,也表示參數的類型也能夠變化,能夠是int,double還能夠是char*,類,結構體等等。可變參數是實現printf(),sprintf()等函數的關鍵之處,也能夠用可變參數來對任意數量的數據進行求和,求平均值帶來方便(否則就用數組或每種寫個重載)。在C#中有專門的關鍵字parame,但在C,C++並無相似的語法,不過幸虧提供這方面的處理函數,本文將重點介紹如何使用這些函數。數組

 

第一步 可變參數表示函數

用三個點…來表示,查看printf()函數和scanf()函數的聲明:spa

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

int scanf(const char *, ...);指針

這三個點用在宏中就是變參宏(Variadic Macros),默認名稱爲__VA_ARGS__。如:orm

#define WriteLine(...) { printf(__VA_ARGS__); putchar('\n');}blog

再WriteLine("MoreWindows");字符串

考慮下printf()的返回值是表示輸出的字節數。將上面宏改爲:get

#define WriteLine (...) printf(__VA_ARGS__) + (putchar('\n') != EOF ? 1: 0);

這樣就能夠獲得WriteLine宏的返回值了,它將返回輸出的字節數,包括最後的’\n’。以下例所示i和j都將輸出12。

       int i = WriteLine("MoreWindows");

       WriteLine("%d", i);

       int j = printf("%s\n", "MoreWindows");

       WriteLine("%d", j);

 

第二步 如何處理va_list類型

函數內部對可變參數都用va_list及與它相關的三個宏來處理,這是實現變參參數的關鍵之處。

在<stdarg.h>中能夠找到va_list的定義:

typedef char *  va_list;

再介紹與它關係密切的三個宏要介紹下:va_start(),va_end()和va_arg()。

一樣在<stdarg.h>中能夠找到這三個宏的定義:

#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) )

來分析這四個宏:

va_end(ap)這個最簡單,就是將指針置成NULL。

va_start(ap,v)中ap = (va_list)&v + _INTSIZEOF(v)先是取v的地址,再加上_INTSIZEOF(v)。_INTSIZEOF(v)就有點小複雜了。( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )全是位操做,看起來有點麻煩,其實否則,很是簡單的,就是取整到sizeof(int)。好比sizeof(int)爲4,1,2,3,4就取4,5,6,7,8就取8。對x向n取整用C語言的算術表達就是((x+n-1)/n)*n,當n爲2的冪時能夠將最後二步運算換成位操做——將最低 n - 1個二進制位清 0就能夠了。

va_arg(ap,t)就是從ap中取出類型爲t的數據,並將指針相應後移。如va_arg(ap, int)就表示取出一個int數據並將指針向移四個字節。

所以在函數中先用va_start()獲得變參的起始地址,再用va_arg()一個一個取值,最後再用va_end()收尾就能夠解析可變參數了。

 

第三步 vfprintf()函數和vsprintf()函數

vfprintf()這個函數很重要,光從名字上看就知道它與常常使用的printf()函數有很大的關聯。它有多個重載版本,這裏講解最經常使用的一種:

函數原型

int vfprintf(

   FILE *stream,

   const char *format,

   va_list argptr

);

第一個參數爲一個FILE指針。FILE結構在C語言的讀寫文件必不可少。要對屏幕輸出傳入stdout。

第二個參數指定輸出的格式。

第三個參數是va_list類型,這個少見,但其實就是一個char*表示可變參參數的起始地址。

返回值:成功返回輸出的字節數(不包括最後的’\0’),失敗返回-1。

vsprintf()與上面函數相似,就只列出函數原型了:

int vsprintf(

   char *buffer,

   const char *format,

   va_list argptr

);

還有一個int _vscprintf(const char *format, va_list argptr );能夠用來計算vsprintf()函數中的buffer字符串要多少字節的空間。

 

 

 

 

代碼範例

下面就給出了本身實現的printf()函數(注1)與WriteLine()函數

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);

}

調用與printf()函數相同。

再給出一個用可變參數來求和,遺憾的在C,C++中沒法肯定傳入的可變參數的個數(printf()中是經過掃描'%'個數來確實參數的個數的),所以要麼就要指定個數,要麼在參數的最後要設置哨兵數值:

設置哨兵數值:

const int GUARDNUMBER = 0; //哨兵標識

//變參參數的個數沒法肯定,在printf()中是經過掃描'%'個數,在這經過設置哨兵標識來肯定變參參數的終止

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;

}

能夠這樣的調用:   printf("%d\n", MySum(1, 3, 5, 7, 9, 0));

但不能夠直接傳入一個0:   printf("%d\n", MySum(0)); //error

指定個數:

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;

}

調用時第一個參數表示後面參數的個數如:

       printf("%d\n", MySum(5, 1, 3, 5, 7, 9));

       printf("%d\n", MySum(0));

代碼所用的頭文件:

#include <stdarg.h>

#include <stdio.h>

 

可變參數的使用方法遠遠不止上述幾種,不過在C,C++中使用可變參數時要當心,在使用printf()等函數時傳入的參數個數必定不能比前面的格式化字符串中的’%’符號個數少,不然會產生訪問越界,運氣很差的話還會致使程序崩潰。

 

可變參數的原形理涉及到調用函數時參數的入棧問題,這個下次再開一篇進行專門的探討。

 

 

 

注1.    網上有不用vfprintf()本身解析參數來實現printf()的,但不多能將功能作到與printf()相近(實際上能徹底熟悉printf()的人已經就很少,不信的話能夠先看看《C陷阱與缺陷》瞭解printf()不少不太經常使用的參數,再去Microsoft Visual Studio\VC98\CRT\SRC中查看OUTPUT.C對printf()的實現)。

注2.    若是輸出單個字符 putchar(ch)會比printf(「%c」, ch)效率高的多。在字符串不長的狀況下,屢次調用putchar()也會比調用printf(「%s\n」, szStr);的效率高。在函數大量調用時很是明顯。

相關文章
相關標籤/搜索