1.原理与实现
C语言可变参数:
void func(char *fmt, ...);
C语言的函数参数占用大小是固定的,参数位置在一块内存中顺序排列,每一个参数占用int(正常32位操作系统4字节)大小,举个栗子:
void func(char c, char *str);
虽然c只占一个字节,但是它后面的3个字节是空着的,用来占位,接下来的就是第二个参数。。
懂得这个原理后,用一个指针就可以遍历完成一个函数的所有参数了。
我们定义C语言的几个宏定义来表达这个想法:
//我们用来访问可变参数的指针类型(无符号指针使得编译器不会告警)。
typedef void * VA_list;
//用来int字节对齐一个参数(不同操作系统的int占用字节数不同,在上个例子中这里就返回4字节)
#define INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
//用来指向可变参数的头部位置,v是可变参数之前的最后一个固定参数 eg: void func(int n, char *fmt, ...);这里的fmt 就是v
#define VA_start(ap, v) (ap = (VA_list)&v + INTSIZEOF(v))
//关键步骤:按int字节递进ap指针,并且返回当前参数类型的参数
#define VA_arg(ap, type) (*(type *)((ap += INTSIZEOF(type))-INTSIZEOF (type)))
//标定可变参数的末尾是空的,代表结束
#define VA_end(ap) ((void)0)
使用这几个宏定义来实现可变参数函数
#include <stdio.h>
int my_print(const char* fmt, ...)
{
VA_list ptr = NULL;
char c;
VA_start(ptr, fmt); //现在ptr指向fmt后面...的第一个参数
while(*fmt != '\0')
{
c = *fmt++;
if(c != '%') {
putchar(c);
} else {
switch(*fmt++)
{
case 's':
printf("%s" ,VA_arg(ptr, char*)); //每使用一次VA_arg()就会递进一次ptr
break;
case 'd':
printf("%d", VA_arg(ptr, int));
break;
case 'c':
printf("%c", VA_arg(ptr, char));
break;
case 'f':
printf("%f", VA_arg(ptr, double));
break;
default:
putchar('\n');
printf ("there is no such a type\n");
break;
}
}
}
VA_end(ptr);
return;
}
int main()
{
my_print("hello %s %d", "world", 520);
}
2.调用库函数实现
C语言库 stdarg.h已经帮我们定义了这些宏定义,我们只要使用它就好了。把上面例子的 VA_xxx 改成小写的va_xxx 即可:
void my_print(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
char c;
while(*fmt != '\0')
{
c = *fmt++;
if(c != '%') {
putchar(c);
} else {
switch(*fmt++)
{
case 's':
printf("%s" ,va_arg(args, char*));
break;
case 'd':
printf("%d", va_arg(args, int));
break;
case 'c':
printf("%c", va_arg(args, char));
break;
case 'f':
printf("%f", va_arg(args, double));
break;
default:
break;
}
}
}
va_end(args);
return;
}
3.可变参函数的封装
如果你正在使用一个可变参数函数,现在你想把该函数封装在一个自己的函数中调用,很不幸的是,你只能舍去原来函数的可变参,把可变参部分写入到固定参数里,因为可变参数没法沿用。
错误的栗子:
#include <stdio.h>
#include <stdarg.h>
void my_print(const gchar *format, ...){
va_list args;
va_start(args, format);
printf(format,args);//错误,printf()函数只会将args看作是一个参数,占用一个参数位,并不会把args看作是多个可变参数。
va_end(args);
}
正确的栗子:
#include <stdio.h>
void my_print(const gchar *format, ...){
va_list args;
char buf[1024];
va_start(args, format);
vsnprintf(buf, sizeof(buf), format, args); //将可变参数的所有字符都写入到buf中。
va_end(args);
printf("%s", buf); //直接打印buf.
}