每一个程序员写的第一个程序大概鼎鼎大名的hello world了,一个简简单单,就那么几行的小程序开启了另一个世界的大门,从此,我们发现了更广阔的世界。而每一个C程序员接触到的第一个函数(除去main函数不算)大概就是printf函数了。这个函数用法很简单灵活,然而里面暗藏玄机,包含了C语言诸多的内容。
不知道大家发现没有,printf函数的参数个数是可变的。下面这三个函数都可以完美运行。
printf("hello,world\n");
printf("this is a int num %d\n",a);
printf("this is a string %s,and a num %d\n",s,a);
上面这三个函数分别有一个、两个、三个参数。而我们学的C语言函数参数的个数都是固定的,比如下面这个swap()函数,必须传入两个int型指针才能运行。
void swap(int *a,int *b)
{
int tmp=*a;
*a=*b;
*b=tmp;
}
而且C语言里面没有类似C++当中类似默认构造函数的机制,那么printf函数参数个数可变是怎么实现的呢?
printf()函数的声明形式是这样:
int printf(char *fmt,...)
第一个参数是char型指针,第二个参数是”…”???正是这个“…”实现了printf的变长参数表。
标准头文件stddrg.h当中包含了一组宏定义,它们对如何遍历参数表进行了定义。
va_list 类型用于声明一个变量,该变量将依次引用各参数。我们将该变量称为ap,意思是“参数指针”。宏va_start将ap初始化为指向第一个无名参数的指针。在使用ap 之前,该宏必须被调用一次。参数表必须至少包括一个有名参数,va_start将最后一个有名参数作为起点。
每次调用va_arg,该函数都将返回一个参数,并将ap 指向下一个参数。va_arg 使用一个类型名来决定返回的对象类型、指针移动的步长。最后,必须在函数返回之前调用va_end,以完成一些必要的清理工作。
下面使用变长参数表实现printf的两个简单功能,打印整数和字符串。
//main.c文件内容
#include<stdio.h>
#include "print_dec.h"
#include<stdarg.h>
void print(char *fmt,...);
int main()
{
int i;
char s[100];
scanf("%d",&i);
scanf("%s",s);
print("this is a int: %d\n",i);
print("this is a string:\n%s\n",s);
return 0;
}
void print(char *fmt,...)
{
int i;
char *m;
va_list p;
va_start(p,fmt);
//将有名参数fmt作为起点,p此时指向第一个无名参数
char *tmp;
for(tmp = fmt;*tmp;tmp++)
{
if(*tmp!='%')
{
putchar(*tmp);
continue;
}
switch(*++tmp)
{
case 'd':
i = va_arg(p,int);
//获得一个无名参数,同时p指向下一个无名参数
print_dec(i);//打印数字i
break;
case 's':
m = va_arg(p,char *);
//获得一个无名参数,同时p指向下一个无名参数
for(;*m;m++)
putchar(*m);
}
}
va_end(p);//结束时的清理工作
}
//print_dec.c文件内容
#include<stdio.h>
void print_dec_(int i)//这个函数可以实现打印正整数
{
if(i == 0)
putchar(i);
else
{
print_dec_(i/10);
int m = i%10 + '0';
putchar(m);
}
}
void print_dec(int i)//实现打印0和正整数
{
if(i==0)
putchar('0');
else
print_dec_(i);
}
//print_dec.h文件内容
void print_dec(int i);
void print_dec_(int i);
//makefile文件内容
TAG=print.out
SRC=main.c print_dec.c print_dec.h
${TAG}:${SRC}
gcc -o ${TAG} ${SRC}
clean:
rm -rf *.out
程序执行效果: