C/C++中可变参数的用法详细解析
可变参数即表示参数个数可以变化,可多可少,也表示参数的类型也可以变化,可以是int,double还可以是char*,类,结构体等等。可变参数是实现printf(),sprintf()等函数的关键之处,也可以用可变参数来对任意数量的数据进行求和,求平均值带来方便(不然就用数组或每种写个重载)。在C#中有专门的关键字parame,但在C,C++并没有类似的语法,不过幸好提供这方面的处理函数,本文将重点介绍如何使用这些函数。
intprintf(constchar*,...);
intscanf(constchar*,...);
这三个点用在宏中就是变参宏(VariadicMacros),默认名称为__VA_ARGS__。如:
#defineWriteLine(...){printf(__VA_ARGS__);putchar("\n");}
再WriteLine("MoreWindows");
考虑下printf()的返回值是表示输出的字节数。将上面宏改成:
#defineWriteLine(...)printf(__VA_ARGS__)+(putchar("\n")!=EOF?1:0);
这样就可以得到WriteLine宏的返回值了,它将返回输出的字节数,包括最后的"\n"。如下例所示i和j都将输出12。
inti=WriteLine("MoreWindows");
WriteLine("%d",i);
intj=printf("%s\n","MoreWindows");
WriteLine("%d",j);
在<stdarg.h>中可以找到va_list的定义:
typedefchar* va_list;
再介绍与它关系密切的三个宏要介绍下:va_start(),va_end()和va_arg()。
同样在<stdarg.h>中可以找到这三个宏的定义:
#defineva_start(ap,v) (ap=(va_list)&v+_INTSIZEOF(v))
#defineva_end(ap) (ap=(va_list)0)
#defineva_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()收尾就可以解析可变参数了。
函数原型
intvfprintf(
FILE*stream,
constchar*format,
va_listargptr
);
第一个参数为一个FILE指针。FILE结构在C语言的读写文件必不可少。要对屏幕输出传入stdout。
第二个参数指定输出的格式。
第三个参数是va_list类型,这个少见,但其实就是一个char*表示可变参参数的起始地址。
vsprintf()与上面函数类似,就只列出函数原型了:
intvsprintf(
char*buffer,
constchar*format,
va_listargptr
);
还有一个int_vscprintf(constchar*format,va_listargptr);可以用来计算vsprintf()函数中的buffer字符串要多少字节的空间。
代码范例
下面就给出了自己实现的printf()函数(注1)与WriteLine()函数
intPrintf(char*pszFormat,...)
{
va_list pArgList;
va_start(pArgList,pszFormat);
intnByteWrite=vfprintf(stdout,pszFormat,pArgList);
va_end(pArgList);
returnnByteWrite;
}
intWriteLine(char*pszFormat,...)
{
va_list pArgList;
va_start(pArgList,pszFormat);
intnByteWrite=vfprintf(stdout,pszFormat,pArgList);
if(nByteWrite!=-1)
putchar("\n");//注2
va_end(pArgList);
return(nByteWrite==-1?-1:nByteWrite+1);
}
调用与printf()函数相同。
再给出一个用可变参数来求和,遗憾的在C,C++中无法确定传入的可变参数的个数(printf()中是通过扫描"%"个数来确实参数的个数的),因此要么就要指定个数,要么在参数的最后要设置哨兵数值:
设置哨兵数值:
constintGUARDNUMBER=0;//哨兵标识
//变参参数的个数无法确定,在printf()中是通过扫描"%"个数,在这通过设置哨兵标识来确定变参参数的终止
intMySum(inti,...)
{
intsum=i;
va_listargptr;
va_start(argptr,i);
while((i=va_arg(argptr,int))!=GUARDNUMBER)
sum+=i;
va_end(argptr);
returnsum;
}
可以这样的调用: printf("%d\n",MySum(1,3,5,7,9,0));
但不可以直接传入一个0: printf("%d\n",MySum(0));//error
指定个数:
intMySum(intnCount,...)
{
if(nCount<=0)
return0;
intsum=0;
va_listargptr;
va_start(argptr,nCount);
for(inti=0;i<nCount;i++)
sum+=va_arg(argptr,int);
va_end(argptr);
returnsum;
}
调用时第一个参数表示后面参数的个数如:
printf("%d\n",MySum(5,1,3,5,7,9));
printf("%d\n",MySum(0));
代码所用的头文件:
#include<stdarg.h>
#include<stdio.h>
可变参数的使用方法远远不止上述几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的"%"符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃。
可变参数的原形理涉及到调用函数时参数的入栈问题,这个下次再开一篇进行专门的探讨。
相关文章
- c++实现简单的web服务器搭建
- C++ 类构造函数&解析函数
- 深入理解C++11_c++ string char
- C++stl库_c++库
- C++学习(一五九)Qt的场景图Scene Graph
- C/C++ Qt 使用JSON解析库 [修改篇]
- 【CMake】Android Studio 中使用 CMake 编译单个 C++ 源文件 ( 常用的 CMake 命令解析 )
- C++实例解析哈夫曼树详解编程语言
- C++ binomial_distribution二项式分布随机数用法解析
- C++学生信息管理系统(文件版)源码下载、源码解析和设计思路
- C语言/C++字符编码方式解析
- 深入解析C++DataMember内存布局
- c++友元函数与友元类的深入解析
- C++输入一个字符串,把其中的字符按照逆序输出的两种方法解析
- C++函数重载的深入解析
- 深入解析C++中的指针数组与指向指针的指针
- C++嵌套类与局部类详细解析
- C++中Operator类型强制转换成员函数解析
- C++中overload,override,overwrite的区别详细解析
- c/c++中变量的声明和定义深入解析
- C++多态的实现及原理详细解析
- C++中栈结构建立与操作详细解析
- C++中函数的默认参数详细解析
- C++中函数模板的用法详细解析
- C++结构体数组详细解析
- c++实现通用参数解析类示例