您的位置:首页 > 其它

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

2011-09-30 19:30 211 查看
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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: