笔试题
printf()函数原型:intprintf(const char*format,[argument]);基本用法就不在赘述了,也不讲一步步实现细节,估计很多人都会看不下去,这些网上都有。接下来主要讲的是如何取数打印出来。
首先,来看一个例子(某知名IT公司的笔试题):
test1.c
#include<stdio.h>
intmain()
{
long long a=1;
long long b=2;
long long c=3;
printf("a=%d\nb=%d\nc=%d\n",a,b,c);
}
上面的程序结果是什么?运行后可以看到,结果是:
a=1
b=0
c=2
看到上面的运行结果,很多人第一眼会很诧异,这里我讲讲我的想法,不知道真实情况是不是我想的这样,仅供参考,不对之处,还望指出。
首先先看内存的中的分布情况。
test2.c
#include<iostream>
#include<stdio.h>
usingnamespace std;
intmain()
{
longlong a=1;
longlong b=2;
longlong c=3;
signedint *m;
m= (int *)&a;
for(int i=0; i < 6; i++)
{
if(i % 2 == 0)
printf("\n");
printf("%p=%d ",m+i,*(m+i));
}
printf("\n");
printf("&a=%p\n",&a);
printf("&b=%p\n",&b);
printf("&c=%p\n",&c);
printf("\n");
printf("a=%d,b=%d,c=%d\n",a,b,c);
printf("a=%lld,b=%d,c=%d\n",a,b,c);
printf("a=%d,b=%lld,c=%d\n",a,b,c);
printf("a=%d,b=%lld,c=%lld\n",a,b,c);
printf("\n\n\n");
longlong n = 8589934592;
int*Ln;
Ln= (int *)&n;
printf("%lld的高字节内容为:%d\n",n,*(Ln+1));
printf("%lld的低字节内容为:%d\n",n,*Ln);
return0;
}
运行结果:
由上面的程序运行结果可知,数据在内存中的分布如下:
地址 |
存的内容 |
0xbffa88c0 |
1 |
0xbffa88c4 |
0 |
0xbffa88c8 |
2 |
0xbffa88cc |
0 |
0xbffa88d0 |
3 |
0xbffa88d4 |
0 |
printf("a=%d,b=%d,c=%d\n",a,b,c);取的是从地址a开始的前三个字节的内容。首先a=%d,从a开始取一个字节,因为用了”%d”,所以输出1,随后b=%d,是紧跟这上次的地址(0xbffa88c4)后取一个字节,而不是从0xbffa88c8开始取的,我想可能printf只获取第二个参数(本例子中的a)的地址,然后根据格式(%d)所代表的长度来取值,而不在乎其后b,c的值是多少,只要有这个参数就行。
为了验证以上想法,我们再来看
printf("a=%lld,b=%d,c=%d\n",a,b,c);很容易知道符合以上想法的。
printf("a=%d,b=%lld,c=%d\n",a,b,c);首先a取了第一个字节即1输出,而后b取从0xbffa88c4到0xbffa88c8的内容,并且本机器是小尾端体系结构,即低地址存低字节,高低值存高字节,比如inta = 0x1234;低地址存的内容是0x34,高字节存的内容是0x12。所以输出结果是8589934592。
printf("a=%d,b=%lld,c=%lld\n",a,b,c);的输出结果同理分析。
printf()实现原理(类源代码):
后来查阅了一下书籍,是这么介绍printf函数的。
函数printf的正确声明形式为:
int printf( char *fmt, ...);
其中,省略号表示参数表中参数的数量和类型是可变的。省略号只能出现在参数表的尾部。printf的关键在于如何处理一个甚至连名字都没有的参数表。标准头文件<stdarg.h>中包含一组宏定义,它们堆如何遍历参数表进行了定义。
va_list类型用于声明一个变量,该变量一次引用各参数。我们自己编写一个printf的简化版函数myprintf。在函数myprintf中,我们将定义一个va_list变量ap,意思是“参数指针”。宏va_start将ap初始化为第一个无名参数的指针。在使用ap之前,该宏必须被调用一次。参数表必须至少包括一个有名参数,va_start将最后一个有名参数最为起点。
每次调用va_arg,该函数都将返回一个参数,并将ap指向下一个参数。va_arg使用一个类型名来决定返回的对象类型、指针移动的步长。最后,必须在函数返回之前调用va_end,以完成一些必要的清理工作。
基于上面的讨论,我们实现的简化printf函数如下所示:
#include <stdarg.h>
/* myprintf函数:带有可变参数列表的简化的printf函数*/
void myprintf( char *fmt, ... )
{
va_list ap; //依次指向每个无名参数
char *p, *sval;
int ival;
double dval;
va_start(ap,fmt);//将ap指向第一个无名参数
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);//结束时的清理工作
}
如有错误,还望指出,谢谢!