可变参数即表示参数个数可以变化,可多可少,也表示参数的类型也可以变化,可以是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(' ');}
  再WriteLine("MoreWindows");
  考虑下printf()的返回值是表示输出的字节数。将上面宏改成:
  #define WriteLine (...) printf(__VA_ARGS__) + (putchar(' ') != EOF ? 1: 0);
  这样可以得到WriteLine宏的返回值了,它将返回输出的字节数,包括后的’ ’。如下例所示i和j都将输出12。
  int i = WriteLine("MoreWindows");
  WriteLine("%d", i);
  int j = printf("%s ", "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*表示可变参参数的起始地址。