可变参数表 -----如何实现printf函数(1)

        printf函数可谓是C语言中使用频度最高的函数,无论是编写或调试程序,我们都无法离开printf,它那便捷的调用方式早已俘获了我们的心。但是,还是那句老话:一个越简单的产品,其实现也就相应地越复杂。好比iphone手机,每个人都喜欢它的简洁易用,但是全世界恐怕没几个人能彻底了解iphone的复杂性。printf的实现有赖于可变参数表,如果没有这个机制,也就没有printf函数的简单易用。
那到底什么式可变参数表呢?
我们先看一下printf函数的原型:
int pritnf(char *fmt, ...);
我们会发现参数表里面居然有省略号!其实这就是可变参数。省略号表示参数表中的参数的数量和类型是可变的。比如,我可以这样调用printf函数:
printf("%f%d%c",2.3,2,'A');
这样printf函数原型实际上就变成了:
int pritnf(char *fmt, float a,int b,char c);
显而易见,那三个点的省略号变成了三个具体的参数,这就是所谓的可变参数表,也就是说省略号是可以根据具体的函数调用来更改参数类型的。但是,必须注意:
1. 只能有一个 ... 并且它必须是最后一个参数;
2. 不要只用一个 ... 作为所有的参数,因为从后面可以知道,这样你无法确定入参表的地址。

关于如何实现可变参数的内部机理,在这篇文章中,我不打算仔细讲,我先教大家如何编写printf函数。
编写可变参数的函数,必须用到3个宏:
va_start,va_arg,va_end,它们都在<stdarg.h>这个头文件中。
我们先说一个三个宏的功能,接着写一个最简单的函数来说明这三个宏是怎么使用的。

       首先,va_list类型用于声明一个变量,改变量将依次引用各参数。也就是说,va_list类型的变量指向函数的参数,也即是一个参数指针。而va_start宏就是用来初始化一个va_list类型变量,将其指向最左边的参数;
接着,每次调用va_arg宏,该函数将返回一个参数,并将参数指针指向下一个参数。va_arg必须使用一个类型名来决定返回的对象类型,指针移动的步长。
最后,必须在函数返回之前调用va_end,完成必要的清理工作。
说过了这些,我们就开始写一个简单的函数来说明下具体实现细节。
测试代码如下:

#include <stdio.h>
#include <stdarg.h>


void test_arg(int a, ...);


int main(void)
{
test_arg(3,6,7,8,9,10,0);
return 0;
}


//在该测试函数中,默认参数均为整型,它将依次打印从左到右的参数
void test_arg(int a, ...)
{
va_list ap;
int next;
va_start(ap,a); //将ap指向第一个无名参数
printf("%3d",a);
next=va_arg(ap,int); //调用完这个宏之后ap会自动指向下一个参数起始的地方
while(next!=0)
{
printf("%3d",next);
next=va_arg(ap,int);
}
printf("\n");
va_end(ap); //清理工作
}

通过这个简单的小例子,我们可以很容易看到这几个宏的用法。那么现在我们就开始说说printf是如何实现的。
这是我们printf的原型:
void my_printf(char *fmt, ...);
首先,我们先来分析一下fmt这个字符串。根据我们使用printf的习惯,对与该字符串,只有以%开头,后面带着一个特殊字母的字符串才会被转换为相应的整数,浮点数,字符串等。如果不是这类情况,则按照其字面值输出。这么一来,结合上面的例子,我们就可以很容易实现这个很函数:
void my_printf(char *fmt, ...)
{
va_list ap;
char *p,*sval;
int ival;
double dval;

va_start(ap,fmt);
for(p=fmt;*p;p++)
{
if(*p!='%') //如果不是占位符,就按照字面值输出
{
putchar(*p);
continue;
}
switch(*++p)
{
case 'd':
ival=va_arg(ap,int);
printf("%d",ival);
break;
case 'f':
dval=va_arg(ap,double);
printf("%f",dval);
break;
case 's':
for(sval=va_arg(ap,char *);*sval;sval++)
putchar(*sval);
break;
default:
putchar(*p);
break;
}
}
va_end(ap);
}
我会在下一篇详细说明这几个宏到底是如何一回事,上面的printf是很不完善的,最大的败笔是使用了printf。利用printf来实现printf,这多少有点鸡生蛋,蛋生鸡的味道。不过想要改过来也很容易,只要将相应的整数和浮点数转换为字符串就行了,这都有专门的函数供我们调用。大家可以仔细思考思考,尽量把这个函数完善到真正的printf。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值