原文地址: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);的效率高。在函数大量调用时非常明显。

转载于:https://www.cnblogs.com/ference/p/3990853.html

C/C++中使用可变参数相关推荐

  1. Golang中支持可变参数

    Golang中支持可变参数 (如果你希望函数带有可变数量的参数) package main import "fmt" //定义一个函数,函数的参数为:可变参数 ... 参数的数量可 ...

  2. C,C++中使用可变参数

    可变参数即表示参数个数可以变化,可多可少,也表示参数的类型也可以变化,可以是int,double还可以是char*,类,结构体等等.可变参数是实现printf(),sprintf()等函数的关键之处, ...

  3. C# 中的可变参数方法(VarArgs)

    首先需要明确一点:这里提到的可变参数方法,指的是具有 CallingConventions.VarArgs 调用约定的方法,而不是包含 params 参数的方法.可以通过MethodBase.Call ...

  4. java 变参 使用数组调用_java中的可变参数使用方法

    java中的可变参数使用方法 可变参数时Java 1.5新增的方法,可变参数方法接收0个或者多个指定类型的参数,可变参数机制通过先创建一个数组,数组的大小为在调用位置所传递的参数数量,然后将参数值传到 ...

  5. 【Python-3.3】函数中的可变参数和关键字参数

    在Python中,函数的参数有以下几种:不可变参数.默认参数.可变参数.关键字参数. 这篇博文主要讲解可变参数和关键字参数. 1. 可变参数 可变参数顾名思义就是参数个数可以变化的参数,可以是0或多个 ...

  6. c语言va_start函数,va_start和va_end,以及c语言中的可变参数原理

    FROM:http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html 本文主要介绍va_start和va_end的使用及原理. 在 ...

  7. python:函数中的可变参数

    可变参数 在不确定参数个数的时候,我们可以使用可变参数 python中的可变参数有两种,在参数前加*或者** 基于元组的可变参数(*可变参数) *可变参数在函数中被组装成一个元组. # 在参数名前面的 ...

  8. Java中的可变参数

    在Java语言中也有可变参数,做法:在最后一个参数类型名后参数名前用"...".编译器会自动使最后一个参数成为参数数组,数组长度就是可变参数的数目,如果没有为可变参数传递一个值,参 ...

  9. C语言中函数可变参数解析

    大多数时候,函数中形式参数的数目通常是确定的,在调用时要依次给出与形式参数对应的所有实际参数.但在某些情况下希望函数的参数个数可以根据需要确定.典型的例子有 大家熟悉的函数printf().scanf ...

  10. 编写可变参数函数 c语言,C语言中编写可变参数函数

    通过stdarg.h头文件为函数提供了定义可变参数列表的能力.声明一个可变参数的函数类似: void f1(int n,...); 其中n表示参数列表个数,而用省略号来表示未知参数列表.stdarg. ...

最新文章

  1. NetDevOps — NETCONF/YANG 协议
  2. 需求用例分析之三:补充规约
  3. thinkpad密码忘记
  4. HDU Problem - 5113 Black And White(搜索剪枝)
  5. 写给省选前的自己V2
  6. selenium自动向下滚动页面,并指定最大滑动距离
  7. Portal for ArcGIS 资源承载数据类型
  8. 如何在Eclipse 3.6.2中安装swt/JFace
  9. 验证GridControl Gridview 单元格。
  10. f.readline()的奇妙坑点
  11. JavaScript-声明变量的关键字
  12. MATLAB--数字图像处理 图像平移
  13. MeGUI入门教程(2012.12.14更新)
  14. 易基因|靶基因DNA甲基化测序(Target-BS)
  15. 为什么很多人打游戏感觉很快乐,然而学习工作中的满足感却很低
  16. 什么是DFX测试.md
  17. 使用AcronisTrueImage 2020迁移thinkpad x1 carbon 2016(4th gen) win10系统到1t的固态硬盘970evoPlus的过程
  18. 惠普1005打印机自检页_hp1005打印机自检报告.docx
  19. 用Iconv应对NodeJs对称加密技术在汉字编码与NoSQL的一些坑洞
  20. wordpress更换模板影响网站SEO吗?

热门文章

  1. Redis执行monitor命令报错 (error) NOAUTH Authentication required.
  2. Mac VMWARE 安装redis
  3. 全国最优秀的计算机学校,2020全国最好的计算机专业学校排名
  4. Struts2之国际化
  5. acm路上的一些感想
  6. Linux环境下Lapack软件包的编译和使用
  7. mysql 存储过程的使用;
  8. python学习笔记之读取配置文件【转自https://my.oschina.net/u/3041656/blog/793467】
  9. 【转】妈妈告诉我的细节
  10. word20161219