C语言实现参数可变函数printf
printf函数的功能相信不用多说,这应该是大家写代码用的最多的一个函数了,那它是怎么做到参数可变的呢。首先此功能实现可变参数的关键在于利用stdarg.h头文件中的三个函数
va_start()
va_arg()
va_end()
然后利用putchar函数输出字符到终端,看代码,我尽量把注释写详细。
#include <stdio.h>
#include <stdarg.h>
//函数声明
void printDec(int dec);
void printHex(int hex);
void printOct(unsigned oct);
void printNum(unsigned long num, int base);
void printStr(char *str);
void printFloat(double f);
void printAddr(unsigned long);
void my_Printf(char *fmt, ...)
{
//可变参数第一步,定义va_list变量
va_list va_ptr;
//可变参数第一步,初始化va_ptr,将va_ptr指向第一个可选参数
va_start(va_ptr,fmt);
//使用循环,打印完所有格式字符串
while(*fmt)
{
//普通字符照常输出
if(*fmt != '%')
{
putchar(*fmt++);
continue;
}
//处理%后要打印的对应格式
switch(*(++fmt))//先++是为了跳过%
{
case 'd'://打印十进制数
printDec(va_arg(va_ptr,int));
break;
case 'c'://打印字符
putchar(va_arg(va_ptr,int));
break;
case 's'://打印字符串
printStr(va_arg(va_ptr,char*));
break;
case 'x':
case 'X'://打印十六进制数
printHex(va_arg(va_ptr,unsigned int));
break;
case 'o'://打印八进制数
printOct(va_arg(va_ptr,unsigned int));
break;
case 'f'://打印浮点数
printFloat(va_arg(va_ptr,double));
break;
case 'p'://打印地址
printAddr(va_arg(va_ptr,unsigned long));
break;
case '%'://打印%
putchar('%');
break;
default:
break;
}
fmt++;//别忘记++,继续查询字符
}
//可变参数最后一步
va_end(va_ptr);
}
//因为用到此函数打印地址,所以函数的第一个参数用unsigned long最为保险
//16位编译器,地址占16位,2字节,可以使用short或者int保存。
//32位编译器,地址占32位,4字节,可以使用int或long保存
//64位编译器,地址占64位,8字节,可以使用long保存。
void printNum(unsigned long num, int base)
{
//递归结束条件
if(num == 0)
{
return;
}
//继续递归
printNum(num/base,base);
//逆序打印结果
putchar("0123456789abcdef"[num%base]);
}
//打印十进制数
void printDec(int dec)
{
//处理负数的情况
if(dec < 0)
{
putchar('-');
dec = -dec;
}
if(dec == 0)
{
putchar('0');
return;
}
printNum(dec,10);
}
//打印十六进制数
void printHex(int hex)
{
if(hex == 0)
{
putchar('0');
return;
}
printNum(hex,16);
}
//打印八进制数
void printOct(unsigned oct)
{
if(oct == 0)
{
putchar('0');
return;
}
printNum(oct,8);
}
//打印字符串
void printStr(char *str)
{
char *p = str;
while(*p)
{
putchar(*p++);
}
}
//打印浮点数
void printFloat(double f)
{
int tmp = (int)f;
//先打印整数部分
printNum(tmp,10);
//小数点
putchar('.');
//再打印小数部分
f -= tmp;
if(f == 0)
{
int i;
//浮点型精度至少6位
for(i = 0; i < 6; i++)
{
putchar('0');
}
return;
}
tmp = (int)(f*1000000);
printNum(tmp,10);
}
void printAddr(unsigned long addr)
{
printStr("0x");
//内存地址是用十六进制表示的
printNum(addr,16);
}
int main(int argc, char *argv[])
{
unsigned long a = 45;
char ch = 'w';
char *str = "hello";
float f = 10.12;
my_Printf("%d %c %s %x %f %o %p %%\n",a,ch,str,567,f,98,&a);
return 0;
}
此函数实现可能在大厂面试时需手动写出来,所以还是要重视一下的,本人亲身经历,在深信服面试时要求手写此功能代码。
总结:
1.需要记住stdarg.h头文件中的三个函数的用法(三部曲:va_start(),va_arg(),va_end())
2.在while循环中对所以字符进行处理,普通字符照常输出,%后的字符在switch中特殊处理
3.在打印各种进制数据中,利用了递归的思想,这点需要深刻理解一下,其实并不难
备注:此案例中有一个小bug,在打印浮点中printFloat函数中,
tmp = (int)(f*1000000)
这一步改变了小数点后半部分的值(大家可以手动打印一下),导致和原生的printf打印出来的有点出入,但是我在网上暂时没有找到解决方案,有没有大神指点一番~