可变参数表 -----如何实现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。
那到底什么式可变参数表呢?
我们先看一下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。
相关文章推荐
- 如何实现VBA中函数的可变参数传递
- C语言可变参数列表详述及实现printf函数
- JavaScript_如何使用arguments实现可变参数_的理解和应用02
- 用可变参数列表模拟实现printf函数
- 用可变参数列表模拟实现printf函数
- 如何实现参数个数可变的函数
- c如何实现可变参数 .
- 如何实现可变参数函数
- 如何实现参数个数可变的函数
- 【C语言】模拟实现printf函数(可变参数)
- 如何在C语言中实现可变参数
- 如何实现可变参数函数
- 如何用 linux 实现命令行参数(可变参数实现)
- C语言实现printf函数,即参数可变函数原理
- c如何实现可变参数
- 模拟实现printf函数,可变参数列表实例
- JavaScript中如何实现函数参数可变
- 用可变参数实现printf函数
- 如何实现函数来处理可变参数
- printf函数可变参数是如何实现的?