C,C++中使用可变参数[va_list、va_start(),va_end()和va_arg()]

深入探讨C++中的可变参数列表(Variadic Templates) 阅读详情

转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/6707662


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

在<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
C/C++可变参数列表参数处理方法va_listva_start()va_copy()va_arg()va_end() C/C++可变参数列表的处理方法 阅读详情

相关推荐

C&C++语言之可变参数传递

“...”省略号在C中可以用作可变参数,printf函数就是通过这个可变参数实现的。(Java也支持这种可变参数) 本期项目中第一次尝鲜开发C++。期间想自己封装一个log函数并遇到了一个可变参数传递的问题。这里做一下代码记录。 #include void debug(char* fmt, )

Zenip的专栏 1466

C,C++使用可变参数

可变参数即表示参数个数可以变化,可多可少,也表示参数的类型也可以变化,可以是int,double还可以是char*,类,结构体等等。可变参数是实现printf(),sprintf()等函数的关键之处,也可以用可变参数来对任意数量的数据进行求,求平均值带来方便(不然就用数组或每种

MoreWindows Blog 4万+

我们来一起聊聊va_listva_startva_argva_end

在C/C++中, 经常会接触到变参, 也就是va(variable argument), 我们的printf函数就是这么做的。 搞过软件开发的人肯定知道, 几乎所有软件的日志函数, 都会采用变参。 下面, 我们来一起聊聊va_listva_startva_argva_end 看一下这些东东在VC++6.0中的定义:

认知 行动 坚持 9097

c++ 可变参数的三种实现方式

一个可变参数模板(variadic template)就是一个接受可变数目参数的模板函数或模板类。存在两种参数包:模板参数包(template parameter packet),表示零个或多个模板参数;说到这里,ubuntu(linux) 默认栈大小8M(使用命令 ulimit -a查看), Win下,Visual Studio 默认栈大小1M(连接器->系统)。函数参数为参数包的形式,使用递归展开。从本质上讲,逗号的作用是将一系列运算按顺序执行。整个逗号表达式的值为系列中最后一个表达式的值。

性感的小君君 9372

【C/C++可变参数

在 c/c++ 中,函数是支持可变参数的,最典型的就是printf()函数,为了支持可变参数,,即最后一个参数位于高地址,第一个参数位于低地址。

涛涛子的博客 1323

C/C++可变参数与默认参数

千万要注意,C不支持默认参数C/C++支持可变参数个数的函数定义,这一点与C/C++语言函数参数调用时入栈顺序有关,首先引用其他网友的一段文字,来描述函数调用,及参数入栈:------------ 引用开始 ------------ C支持可变参数的函数,这里的意思是C支持函数带有可变数量的参数,最常见的例子就是我们十分熟悉的printf()系列函数。我们还知道在函数调用时参数是自右向左压

1792

C C++使用可变参数

C C++使用可变参数

qq_43678306的博客 407

C语言函数之可变参数原理:va_startva_argva_end

说到C语言函数可变参数,我们最先想到的可能就是printf、scanf、printk了。在Linux-2.6.24.7内核源码里,printk函数原型如下: asmlinkageintprintk(constchar*fmt,...) asmlinkage表示通过堆栈传递参数。gcc编译器在汇编过程中调用c语言函数时传递参数有两种方法:一种是通过堆栈,另一种是通过寄存器。缺省时采用寄存器,假如你要在你的汇编过程中调用c语言函数,并且想通过堆栈传递参数,你定...

stephenbruce的博客 9241

va_list/va_start/va_end/va_arg可变参数使用

做日志打印或其它可变参数处理时,通常我们会想到使用va_list/va_start/va_end可变参数的收集处理。使用这种方式处理可变参数比较通用,同时适用于c与c++中。

春夜喜雨的专栏 1684

C++可变参数

参数列表的… … 表示函数的参数个数可变,典型的如printf() int printf (const char * szFormat, ...); 第一个参数是一个格式化字符串,后面是与格式化字符串中的代码相对应的不同类型的多个参数。 char* name = "fgq"; int age = 18; printf("info {name:%s, age:%d}\n",name, age) ; 使用…实现变参数函数的两种场景 1. 格式化字符串 使用场景: 类似于实现一个printf,输入一串格

weixin_39488566的博客 6609

C++可变参数列表

可变参数列表 可变参数模板 std::initializer_list

shouhu010的博客 618

C/C++编程:可变参数

_VA_ARGS变长参数的宏定义是指在宏定义中参数列表的最后一个参数为省略号,而预定义宏__VA_ARGSprintf就可以定义一个printf的别名PR。

OceanStar的博客 9099

C++开发基础——可变参数可变参数模板

C++语法基础

程序员与背包客_CoderZ 2845

【C/C++可变参数列表

本篇讲解可变参数列表

zzg的学习博客 1433

c/c++可变参数列表

1. c语言的可变参数列表 va_list ptr: 定义一个指向可变参数列表的指针 va_start(ptr, a):初始化指针,其中第二个参数为函数可变参数列表之前的固定参数 va_arg(ptr,int):取出指针指向的元素,第二个参数为元素的类型,返回值为取出的元素,同时指针后移。 va_end(ptr) : 还原ptr指针 #include<stdarg.h> // 确定:要...

Nick_Zhang_CSDN的博客 2091

C++中函数的可变参数列表

** 1. 缺省参数实现可变参数列表(差的方案)** 当我们在写一个参数个数未知而类型确定的函数(如计算多个整型数字的平均数)时,可以用函数的第一个参数表示后面的参数个数,并定义多个缺省参数,在函数体内读取相应个数的参数。 #include <iostream> using namespace std; float average(int number, int a, int b = 0, int c = 0, int d = 0, int e = 0) { float sum = a;

qq_54180412的博客 3815
上一篇: [转]浅析 Linux 中的时间编程和实现原理,第 1 部分: Linux 应用层的时间编程
下一篇: MFC中CHtmlView和JS的相互调用
__Andy
博客等级 码龄14年 2粉丝 1原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值