今天在《征服C指针》的书里面看到了一个很有意思的函数写法。即写一个输入的参数数量是可变的。
一个典型的可变输入函数就是printf(),该函数根据解析字符串中的%s, %d,%p等,就可以知道后面带有多少个参数。
但实际上,由于c语言数据的压栈方式,我们只需要知道第一个参数的位置,后续的参数就可以按顺序读取。也就是说我们要做的就是告诉函数,参数的起止,以及参数的格式,让函数能正确地读取参数。
ANSI C通过头文件stdarg.h提供了一组可用于可变参数的宏。代码如下。
c
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
void tiny_printf(char *format, ...)
{
int i;
va_list ap;
va_start(ap, format);
for(i = 0; format[i] != '\0'; i++)
{
switch (format[i])
{
case 's':
/* code */
printf("%s ", va_arg(ap, char*));
break;
case 'd':
/* code */
printf("%d ", va_arg(ap, int));
break;
default:
assert(0);
}
}
va_end(ap);
putchar('\n');
}
int main(void)
{
tiny_printf("ssdd", "hu", "cong", 6, 6);
return 0;
}
注意到上述的代码,输入处的"..."代表着输入处不用进行参数的检查。
其中声明了va_list 类型的变量,可以看作某种指针,va_start(ap, format);代表将ap指向format后面的下一参数的位置,然后在循环中每次调用va_arg都会指向下一个参数,然后通过format中的指示,告诉我们第几个参数是哪种格式,就能正确地读取参数。比如上述代码中"ssdd"代表第一个和第二个是字符串,三个和第四个是整型。如此可依次取出所有参数。
va_end是对应于va_start的函数,在可变长参数的传递过程中,由va_start分配内存并将参数保存其中,最后由va_end释放内存。
输出结果如下:
hu cong 6 6
注意:代码中出现了许多单引号和双引号,很多时候他们并不能相互替换,因为其表示的含义不同。
'A' 表示单个字符大写字母A,占用1个字节空间
"A" 表示字符串,该字符串只有1个大写字母A组成,占用2个字节空间,每个字符串末尾自动会加上一个空字符 '\0'
空字符常量使用转义符号 '\0'表示,空白字符串使用双引号表示 ""