Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2793784
  • 博文数量: 154
  • 博客积分: 7136
  • 博客等级: 准将
  • 技术积分: 1428
  • 用 户 组: 管理员
  • 注册时间: 2010-02-21 11:26
文章分类

全部博文(154)

文章存档

2016年(2)

2014年(2)

2013年(4)

2012年(16)

2011年(51)

2010年(68)

2009年(3)

2006年(3)

2005年(5)

分类:

2011-08-18 16:18:52

  1. C/C++提供了函数的可变参数(variadic)机制。printf就是一个使用可变参数的典型,它的原型声明为,

  2. int printf(const char *fmt, ...);
  3.   其中返回值为实际输出字符个数,fmt为格式控制字符串,而”…”便声明了一个可变参数,
  4. 你可以根据传递0个或多个参数给printf。printf内部会根据格式控制串中的格式指定符号(d, f, p等等)
  5. 来逐个解析通过可变参数传进的实参变量。

  6.   为解析可变参数,C语言提供了一个va_list类型和四个宏,分别是va_start, va_arg, va_end, 和va_copy,
  7. 这些宏声明在stdarg.h中。为了方便描述,下面实现一个简单的类似printf的函数:

  8. void mockprintf(const char *fmt, ...)
  9. {
  10.     va_list ap;
  11.     va_start(ap, fmt);
  12.     for (const char *s = fmt; *s; ++s)
  13.     {
  14.         switch(*s)
  15.         {
  16.             case 'd':
  17.                 printf("meet d\n");
  18.                 int d = va_arg(ap, int);
  19.                 printf("%d\n", d);
  20.                 break;
  21.             case 's':
  22.                 printf("meet s\n");
  23.                 const char *str = va_arg(ap, char*);
  24.                 printf("%s\n", str);
  25.                 break;
  26.             case 'c':
  27.                 printf("meet c\n");
  28.                 char c = va_arg(ap, int);
  29.                 printf("%c\n", c);
  30.                 break;
  31.             default:
  32.                 printf("unknown format specifier\n");
  33.         }
  34.     }
  35.     va_end(ap);
  36. }

  37. int main()
  38. {
  39.     mockprintf("cdfs", 'A', 0x45, "string");
  40.     return 0;
  41. }
  42.   va_list的实现与编译器和平台相关,通常是一个指向参数栈的指针。va_start使用变参列表前的最后一个命名参数(named argument)作为参数,
  43. 以此定位变参列表的第一个参数的地址,并将ap指向该参数(此处假设va_list实现为指针)。宏va_arg需要两个参数,va_list变量和下一个预期的参数的类型,
  44. 该宏以指定类型返回(展开为)对应的参数值,并调整va_list指向下一个参数。最后,每一个va_start需要一个va_end作为结束。
  45. 另外,示例函数中没有用到va_copy,这是一个用来复制va_list变量到另一个va_list变量的宏,目的是应对平台间va_list实现的差异。
  46.   值得一提的是,va_start和va_end可以重复调用,用以多次对变参列表进行解析。

  47. printf家族
  48.   C的printf家族包含8个成员,原型如下,

  49. #include <stdio.h>
  50. int printf(const char *fmt, ...);
  51. int fprintf(FILE *stream, const char *fmt, ...);
  52. int sprintf(char *str, const char *fmt, ...);
  53. int snprintf(char *str, size_t size, const char *fmt, ...);
  54.  
  55. #include <stdarg.h>
  56. int vprintf(const char *fmt, va_list ap);
  57. int vfprintf(FILE *stream, const char *fmt, va_list ap);
  58. int vsprintf(char *str, const char *fmt, va_list ap);
  59. int vsnprintf(char *str, size_t size, const char *fmt, va_list ap);
  60.   前四个函数没有什么特殊的。后面四个v系列可以接受va_list变量,通常用在对可变参数输出的包装,在日志记录系统中较为常用。比如下面代码,

  61. enum LogLevel { ERROR, WARN, INFO, DEBUG }
  62. void log(LogLevel level, const char *fmt, ...)
  63. {
  64.     va_list ap;
  65.     va_start(ap, fmt);
  66.     vsnprintf(buf, sizeof(buf), fmt, ap);
  67.     va_end(ap);
  68.     //~ write buf to file, or do something else.
  69. }
  70.   v系列函数并不会调用va_end宏,因此在这些函数返回后,需要调用函数自己进行va_end。若要再次解析变参列表,就需要重新va_start, va_end。

  71. 宏变参
  72.   除了函数,在C/C++中,带参宏定义也可以接受变参,使用方法和函数类似。比如,若将上面的log函数的某个级别的日志输入定义成宏,

  73. #define log_warn(fmt, ...) log(WARN, fmt, __VA_ARGS__)
  74.   __VA_ARGS__只是被预处理器简单的展开为传递给宏log_warn的变参列表,包括逗号分隔符。若想使用具有鲜明意义的名字,而不是统一的__VA_ARGS__,可以这样,

  75. #define log_warn(fmt, args...) log(WARN, fmt, args)
  76.   上述宏定义中,有一个问题值得注意,就是当变参列表为空时,log函数调用的参数列表会有一个结尾的逗号,这在某些编译器中会被诊断为错误(据说MSVC不会),这种情况下可以将fmt也纳入变参列表,

  77. #define log_warn(...) log(WARN, __VA_ARGS__)
阅读(1628) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~