C和C++中的可变参数及宏的使用 您所在的位置:网站首页 可变参数的使用 C和C++中的可变参数及宏的使用

C和C++中的可变参数及宏的使用

2024-07-10 09:49| 来源: 网络整理| 查看: 265

可变参数

可变参数即表示参数的个数可以变化,可多可少,也表示参数的类型也可以变化,可以是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(...) { 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及与它相关的三个宏来处理,这是实现变参调用的关键之处。

在中可以找到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) )

( (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就可以了。

分析:为什么是int的倍数?

因为大多数编译器是push一次都是push一个DWORD(4字节),所以哪怕传的第一个是char,也会自动补成4字节。在VS里反汇编了一下,结果如下:

f(c,i); 00411539 mov eax,dword ptr [i] 0041153C push eax 0041153D movzx ecx,byte ptr [c] 00411541 push ecx 00411542 call f (411023h) 00411547 add esp,8 //可看出push 4字节的。 va_end(ap)

这个最简单,就是将指针置成NULL。

va_start(ap,v)

先取v的地址,再加上_INTSIZEOF(v)。

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()函数相同。

注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/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


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有