Printf
上一篇的“Helloworld”用到了大家最熟悉的printf,那这次我们就来了解一下它的原理,当然,最好的了解方式就是仿照printf自己写一个出来。
把”World”当做参数传递给printf:
printf(“Hello, %s\n”,”World”);
参照上篇,我们可以知道,前面的” Hello, %s\n” 是参数 char * fmt ,后面为省略参数。
思路:
第一个参数是字符指针,此刻正指向’H’,我们从它出发把前面的fmt扫描一遍完成解析。
解析过程:
1. 当遇到简单的字符以及换行符时,简单地写进最后的buffer里面就了。
2. 遇到”%s”的时候,我们开始解析,这时对应的是”World”这个常量字符串指针,把它扫一遍填进buffer就OK。
实现:
1. 扫描,那就需要一个循环,循环里每次向前走一步就OK,这个没问题。
2. 我需要知道”World”的地址,这时上篇说的va_start(args,fmt)让args指向fmt参变量所占空间+1的地址,也就是第二个参数的地址,可以拿到。再用va_arg(args,char*)我可以得到一个char *指针,也就是“World”的首地址,并且args移向下一个参数的首地址。
这里分析一下va_arg这个宏
#define _crt_va_arg(ap,t) ( *(t *)( (ap += _INTSIZEOF(t)) -_INTSIZEOF(t) ) )
1. (ap +=_INTSIZEOF(t)) => x 这里可以看见ap 移动了 t这么长的位置,假设现在就是x.
2. ( x -_INTSIZEOF(t) ) => ap 这里没有往回走,但是得到的结果是刚才ap的位置。
3. (t *) (ap) =>pt 把ap强制转换为t*的指针类型。
4.(*pt) 最后取值。
所以这个宏做了两件事情,一个是让ap移动了t所占大小的位置,另一个是返回ap的地址并强转为t*类型再取值。
我们写个函数来测试一下:
void TestVaMacro(int para1, double para2, char* para3)
{
va_list args;
va_start(args,para1); //拿到第二个参数para2的地址
double val2 =va_arg(args, double); //args向前移动sizeof(double),指向下一个参数(第三个), 返回第二个参数的地址并取值
char *val3 =va_arg(args, char*); //args向前移动sizeof(char*),指向下一个参数(已经没了),返回第三个参数的地址并取值
va_end(args);
}
int _tmain(int argc, _TCHAR* argv[])
{
TestVaMacro(1,3.22, "the string");
return 0;
}
这里如愿获得了分别对应的值
那这样我们就可以写一个简单版的printf了:
void minprintf(char *fmt,...)
{
va_list args;
va_start(args,fmt);
while(isspace(*fmt)) //前面的空格不要
{
fmt++;
continue;
}
while (*fmt) //开始扫描
{
if(*fmt!= '%') //还没遇到’%’号,那就简单打印出来,跳到下一个字符
{
putchar(*fmt++);
continue;
}
++fmt; //这里已经遇到,那就看’%’后面接的是什么
if(isalnum(*fmt)) //遇到数字了,这里写的是几就填充几个空格
{
intlen = *fmt-'0';
char*buf = new char[len+1];
memset(buf,0, len+1);
inti = 0;
while(i < len)
{
buf[i]= ' ';
i++;
}
printf("%s",buf);
delete[]buf;
buf= NULL; //用完置空,好习惯
fmt++;
}
switch(*fmt) //看数字后面接的什么字母
{
case'd':
{
intval = va_arg(args,int); //是个整形
printf("%d",val);
break;
}
case'f':
{
doubleval = va_arg(args,double); //是个double
printf("%f",val);
break;
}
case's':
{
char*str = va_arg(args,char*); //是个char *字符串
printf("%s",str);
}
}
fmt++;
}
va_end(args);
}
虽然不完善,我们也已经实现了一个简单版本的printf,当然,相比于工业级的实现(上一篇微软的实现)肯定是差了很多的,但是相信通过这样,大家对它应该有了一个更好的理解。