C语言不定型参数函数定义
我们在C语言中定义一个函数,通常都是需要在函数原型中规定这个函数需要提供什么类型的参数以及需要提供多少个。也就是,你的参数必须明确。但是我们调用函数库中的printf和scanf函数会发现,它们似乎是可以根据我们自己任给的参数类型与参数个数来操作,那它们是怎么实现的呢?
在《C程序设计语言》中我找到了相关的描述,内容位于第七章输入与输出中的7.3节:可变参数表。
书中指出:我们想使用可变参数的函数时,首先应当在函数声明中用三个‘.’来代替将来使用可能会出现的参数,且省略号必须位于参数表的尾部。如:
int Test(char *format,...);
而非是
int Test(...,char *format);
当然,除了格式控制符,你还可以提供更多的参数表,以完成你需要的操作。
上面的操作我们只是完成了函数的声明,而函数体的完成,我们需要引入标准头文件<stdarg.h>。里面提供的一些宏可以帮助我们实现可变参数表的遍历。
首先,它提供了一种参数指针的类型:va_list。我们可以使用它来定义一个指针遍历参数表。如
va_list ap;
在我们指针正式工作前,还必须使用宏va_start对ap进行初始化,结果是使它指向第一个无名参数。(事实上更准确的描述应该是:以最后一个有名参数为起点,因为事实上是可以不传入无名参的。) 我们需要遍历参数表时,使用宏va_arg来实现。它会将指针在参数表往后挪一个,并给你返回一个值。它需要你自己提供一个参数类型,并把指的这个参数内容按你提供的类型解析。这个参数类型需要你自己对format进行解析得到。当我们完成遍历工作后,还需要使用宏va_end来完成系列清理工作。注意:va_start、va_arg和va_end在头文件中都是采用宏的方式,不信你#undef试试。
好啦,下面就是我们自写的一个myPrintf函数。
#include<stdio.h> #include<stdarg.h> //#undef va_end void myPrint(char *fmt,...); int main(void) { int i = 10; double d = 23.1; char str[] = "HelloWorld"; myPrint("this is a intVal:%d\n",i); myPrint("this is a doubleVal:%f\n",d); myPrint("this is a intVal:%d\n,this is a char *info:%s\n",i,str); myPrint("this is a test\n"); return 0; } void myPrint(char *fmt,...) { //使用va_list类型声明变量 它会依次引用各个参数 va_list ap; char *p,*sval; int ival; double dval; int count = 0; va_start(ap,fmt); //宏va_start将ap指向第一个无名参数 在使用ap前该宏必须被调用一次 参数表至少包含一个有名参数 for(p = fmt;*p;p++) { if(*p != '%') { putchar(*p); continue; } count++; //如果是碰到了% 则判断后一个字符是什么 switch(*++p) { case 'd': ival = va_arg(ap,int); printf("%d %d",count,ival); break; case 'f': dval = va_arg(ap,double); printf("%d %f",count,dval); break; case 's': printf("%d ",count); //先把ap中的对应信息取出来放这里:s_val 然后对这个s_val遍历输出 for(sval = va_arg(ap,char *);*sval;++sval) putchar(*sval); break; default: putchar(*p); break; } } va_end(ap); //结束需要清理工作 }
当然,我们只是实现了阉割版的printf,其中还缺乏很多功能呢。比如宽度控制、对齐控制以及更多种类型的输出等等都有待完善。