C C++中使用可變引數
第一步 可變引數表示
用三個點…來表示,檢視printf()函式和scanf()函式的宣告:
int printf(const char *, ...);
int scanf(const char *, ...);
這三個點用在巨集中就是變參巨集(Variadic Macros),預設名稱為__VA_ARGS__。如:
#define WriteLine(...) { printf(__VA_ARGS__); putchar('\n');}
再WriteLine("MoreWindows");
考慮下printf()的返回值是表示輸出的位元組數。將上面巨集改成:
#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);的效率高。在函式大量呼叫時非常明顯。
轉載請標明出處,原文地址:http://blog.csdn.net/morewindows/article/details/6707662
再分享一下我老師大神的人工智慧教程吧。零基礎!通俗易懂!風趣幽默!希望你也加入到我們人工智慧的隊伍中來!https://www.cnblogs.com/captainbed