C C,C 中使用可變引數

2022-05-26 12:42:09 字數 3436 閱讀 5241

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

第一步 可變引數表示

用三個點…來表示,檢視printf()函式和scanf()函式的宣告:

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

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

這三個點用在巨集中就是變參巨集(variadic macros),預設名稱為__va_args__。如:

#define writeline(...)

再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及與它相關的三個巨集來處理,這是實現變參引數的關鍵之處。

在中可以找到va_list的定義:

typedef char *  va_list;

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

同樣在中可以找到這三個巨集的定義:

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

int writeline(char *pszformat, ...)

可以這樣的呼叫:   printf("%d\n", mysum(1, 3, 5, 7, 9, 0));

但不可以直接傳入乙個0:   printf("%d\n", mysum(0)); //error

指定個數:

int mysum(int ncount, ...)

呼叫時第乙個引數表示後面引數的個數如:

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

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

**所用的標頭檔案:

#include

#include

可變引數的使用方法遠遠不止上述幾種,不過在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);的效率高。在函式大量呼叫時非常明顯。

可變引數使用

在c中,可變引數用於引數個數,型別不確定的情況,如printf,snprintf函式的實現。當我們無法列出傳遞函式的所有實參的型別和數目時,可以用省略號指定參數列 void func void func parm list,這是c傳參的一種形式,與固定引數不同。函式引數以棧的形式儲存,從右往左入棧。...

可變引數及可變引數巨集的使用

我們在c語言程式設計中會遇到一些引數個數可變的函式,例如printf 這個函式,這裡將介紹可變函式的寫法以及原理.一般在除錯列印debug 資訊的時候,需要可變引數的巨集.從c99開始可以使編譯器標準支援可變引數巨集 variadic macros 另外gcc 也支援可變引數巨集,但是兩種在細節上可...

可變引數函式使用

va函式的定義和va巨集 c語言支援 va函式,作為 c語言的擴充套件 c 同樣支援 va函式,但在 c 中並不推薦使用,c 引入的多型性同樣可以實現引數個數可變的函式。不過,c 的過載功能畢竟只能是有限多個可以預見的引數個數。比較而言,c中的 va函式則可以定義無窮多個相當於 c 的過載函式,這方...