在我们写过以往的代码中频繁使用printf函数,printf函数在传参过程中其参数其实是不固定的
printf("hello world\n");//1个参数
printf("%s", "hello world\n");//2个参数
printf("%s %s","hello","world\n");//3个参数
我们可以通过以下简单的代码来分析可变参数列表
#include <stdio.h>
#include <stdarg.h>
int Average(int n, ...)
{
va_list arg;
int i = 0;
int sum = 0;
va_start(arg, n);
for (i=0; i<n; i++)
{
sum += va_arg(arg, int);
}
return sum/n;
va_end(arg);
}
int main()
{
int ret = 0;
ret = Average(3, 1, 2, 3);
printf("%d\n", ret);
ret = Average(4, 5, 6, 7, 8);
printf("%d\n", ret);
return 0;
}
上面代码中几个陌生的点(实现可变参数):
声明一个va_lisi类型的变量arg,它用于访问参数别表未确定的部分
va_start用于初始化变量arg。它的第一个参数是va_lisi的变量名,第二个参数是省略号前最后一个有名字的参数。初始化过程把arg变量设置为指向可变参数部分的第一个参数。
va_arg用于访问参数,此宏接受两个参数:va_list变量和参数列表中下一个参数的类型。va_arg返回这个参数的值,并使用va-arg指向下一个可变参数。实现此功能的代码非常经典,值得学习(在此代码中宏替换后的结果):
( *(int *)((arg += 4) - 4) )
va_end使使用完后的指针变量赋成空指针。
可见:可变参数的实现过程实际上是使用宏进行封装,只要完成替换便一目了然
上面代码的运算结果:
分析完成这一段代码,就可以对printf函数进行简单的模拟
printf函数并没有直接的给出可变参数列表的参数个数,但是它在第一个字符串的内部,通过输出格式间接给出了这个个数,所以如果要拿到这个值,必须对第一个字符串进行解析- 如果是整形按照递归的写法拿出这个整形的每一位,并用putchar函数以其对应的字符输出
- 如果是字符串,循环遍历整个字符串,并用putchar输出每一个字符,遇到
'\0'
停止 - 如果是字符直接putchar输出
代码如下:
#include <stdarg.h>
void show(int n)
{
if(n>9)
{
show(n/10);
}
putchar(n%10+'0');
}
void print(const char *format, ...)
{
va_list arg;
va_start(arg, format);
while(*format)
{
switch(*format)
{
case 's':
{
char *ret = va_arg(arg, char*);
while(*ret)
{
putchar(*ret);
ret++;
}
}
break;
case 'd':
{
int ret = va_arg(arg, int);
show(ret);
}
break;
case 'c':
{
int ch = va_arg(arg, char);
putchar(ch);
}
break;
default:
putchar(*format);
break;
}
format++;
}
}
int main()
{
print("d s c\n", 100, "hehe", 'w');
return 0;
}
运算结果:
当然库函数中的printf函数实现远比这复杂的多,有待于今后的进一步研究。