参考:
stdarg.h:https://zh.wikipedia.org/wiki/Stdarg.h
stdarg.h:http://baike.baidu.com/view/3373010.htm
linux环境下可以使用man手册:man stdarg
#################################################################
C语言也存在可变参数的概念
最常见的就是scanf和printf函数:
int scanf(const char * restrict format,...);
int printf(const char *fmt, ...);
你可以输入任意类型的任意个参数,但是必须在格式化字符串中确定输入参数的个数和类型。
那么我们如何自定义可变参数函数呢?
就需要使用stdarg.h头文件了。stdarg的全称就是standard arguments(标准参数),主要目的就是为了让函数能够接收可变参数。
它为用户定义了4个标准宏:
/* Define the standard macros for the user,
if this invocation was from the user program. */
#ifdef _STDARG_H
#define va_start(v,l) __builtin_va_start(v,l)
#define va_end(v) __builtin_va_end(v)
#define va_arg(v,l) __builtin_va_arg(v,l)
#if !defined(__STRICT_ANSI__) || __STDC_VERSION__ + 0 >= 199900L || defined(__GXX_EXPERIMENTAL_CXX0X__)
#define va_copy(d,s) __builtin_va_copy(d,s)
#endif
注意:如果想要使用stdarg.h中的宏定义和类型对象,必须显示定义头文件#include
接下来先介绍4个宏定义:
void va_start(va_list ap, last);
参数last指的是变量参数列表之前的参数名,也就是调用函数中最后一个已知参数类型的参数。比如,printf函数中的fmt
因为last参数的地址会在va_start函数中使用,所以last不应该是一个寄存器变量,函数或者数组类型。
type va_arg(va_list ap, type);
参数ap就是va_start初始化的va_list对象;
参数type是一个类型名,比如“char”,“int”等,表示当前ap指向的参数的类型
每次调用va_arg后,ap就会指向下一个参数。但如果已经遍历完参数列表,或者参数type并不是当前参数的实际类型名,此时调用va_arg函数将会发生随机错误。
ap被参数va_arg函数使用过后,将无法回到最开始的位置
void va_end(va_list ap);
va_end函数和va_start相对应。在同一个函数中,调用过va_start之后就必须调用va_end。
使用va_end以后,变量ap将重置为空,并释放内存。
void va_copy(va_list dest, va_list src);
每次调用过va_copy函数后,必须相应的在同一个函数中调用va_end函数,比如:
va_list aq;
va_copy(aq, ap);
...
va_end(aq)
注意:va_start/va_arg/va_end函数符合C89标准。而va_copy是C99定义的
#################################################333
学习完stdarg.h之后,我们使用几个函数来练习一下:
#include
#include
void foo(char *fmt, ...)
{
va_list ap;
int d;
char c, *s;
va_start(ap, fmt);
while (*fmt)
switch (*fmt++) {
case 's': /* string */
s = va_arg(ap, char *);
printf("string %s\n", s);
break;
case 'd': /* int */
d = va_arg(ap, int);
printf("int %d\n", d);
break;
case 'c': /* char */
/* need a cast here since va_arg only
takes fully promoted types */
c = (char) va_arg(ap, int);
printf("char %c\n", c);
break;
}
va_end(ap);
}
int main(int argc, char* argv)
{
foo("%d %s %c", 23, "hello", 'z');
}
由于可变参数函数的参数数量不定,C语言定义了省略号来表示之后的参数列表,但最少要有一个确定类型的参数(C++中可以忽略)
#include
#include
void printargs(int arg1, ...) /* 输出所有int类型的参数,直到-1结束 */
{
va_list ap;
int i;
va_start(ap, arg1);
for (i = arg1; i != -1; i = va_arg(ap, int))
printf("%d ", i);
va_end(ap);
putchar('\n');
}
int main(void)
{
printargs(5, 2, 14, 84, 97, 15, 24, 48, -1);
printargs(84, 51, -1);
printargs(-1);
printargs(1, -1);
return 0;
}
这是百度百科里的一个例子。printargs假定输入的参数均为int类型的整数,将所有参数输出,直到遇到-1为止(好像,维基百科也是这样的)
接下来写一个比较完整的程序,类似于printf函数,能够在fmt中输入非格式化字符,同时能够输入格式化参数
#include
#include
void printff(const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
int i=0;
while (fmt[i] != '\0')
{
if (fmt[i] != '%')
{
printf("%c", fmt[i]);
i ++;
continue;
}
fmt ++; // 跳过%
switch(fmt[i])
{
case 'c': // 得到一个字符
char cc;
cc = (char)va_arg(ap, int);
printf("%c", cc);
break;
case 'd': // 得到一个整数
int dd;
dd = (int)va_arg(ap, int);
printf("%d", dd);
break;
case 's': // 得到一个字符串
char *ss;
ss = va_arg(ap, char*);
printf("%s", ss);
break;
}
va_end(ap);
}
}
int main(int argc, char* argv)
{
printff("Hello World\n");
printf("%d Hi %s 2233 %c\n", 2, "adfa", 'a');
}
这样子就能简单实现自定义参数函数的效果