简单printf实现

来源:互联网 发布:乐乎青年公寓官网 编辑:程序博客网 时间:2024/06/05 16:05
 

实现简单的printf函数

 4380人阅读 评论(2) 收藏 举报
 分类:
首先,要介绍一下printf实现的原理

printf函数原型如下:
[cpp] view plain copy
 print?
  1. int printf(const char* format,...);  

返回值是int,返回输出的字符个数。

例如:
[cpp] view plain copy
 print?
  1. int main()  
  2. {  
  3.     int n;  
  4.     n=printf("hello world,%d\n",100);  
  5.     printf("返回值:%d\n",n);  
  6.     return 0;  
  7. }  


测试结果:

hello world,100

返回值:16

测试结果是16,是因为100虽然是整型数,但是输出时计算返回值它是3个字符。



参数format是一个字符指针,指向printf里的第一个字符串。

参数...是不定参数。这是printf能够实现的核心。

接下来介绍一下不定参数是如何实现的。

int printf(const char* format,...);

函数的参数由右向左依次入栈,如下图:



比如我们printf实际输入的参数有4个,printf(char* format,arg1,arg2,arg3,arg4);

这些参数在内存中从低地址到高地址依次为format,arg1,arg2,arg3,arg4。

因为format是指针,所以所占的字节大小为一个int的大小。

所以如果我们找到format的储存地址,从format首地址开始,加上一个int的大小,此时地址刚好就是参数arg1的首地址,然后再加上sizeof(arg1),此时地址又刚好是arg2的首地址,这样我们就能依次找出参数所在地址。

具体实现时,我们只需要定义一个指针变量ap指向arg1参数的起始地址,同时分析format参数所指的字符串,从字符串第一个字符开始检查,如果遇到%则通过分析%后面的字符就能判断出变量的类型,此时输出ap地址上所指向的变量的值,同时ap指针向右移动该变量类型大小字节个单位,使ap指向下一个参数的储存地址,然后再次分析字符串,直到分析到字符串结尾结束。

通过上面的参数入栈方式我们可以得到如下结论:


如果想将栈中的参数读出来,我们只需要知道,栈顶元素的地址即第一个参数的地址即可。通过前面变参函数的分析,通过变参函数第一个参数可以知道传递的参数个数。

当然,每个参数都有自己的类型,还有的就是字节对齐了。在读取参数的时候,这些问题都必须考虑到。

实际上处理变参时,已经有封装好的宏处理这些所有问题

[cpp] view plain copy
 print?
  1. typedef   char  * va_list;                  //将char*别名为va_list;  
  2.   
  3.           
  4.   
  5. #define   _INTSIZEOF(n)   ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))    
  6.   
  7. #define   va_start(ap,v)   (ap = (va_list)&v + _INTSIZEOF(v))  
  8.   
  9. #define   va_arg(ap,t)     (*(t*)((ap += _INTSIZEOF(t)) -  _INTSIZEOF(t)))  
  10.   
  11. #define   va_end(ap)      (ap = (va_list)0)  




这些宏在不同的操作系统,有不同的实现,想使用的话,只需要包含头文件stdarg.h就可以了。

(1)va_start宏的作用 : 

printf(const char* format,arg1,agr2,....)

实现ap指向第一个实际参数arg1的地址,实际参数指第一个参数format后的第一个参数arg1。即va_start(ap,format)。

(2)va_arg宏作用:

t指的是分析出来的实际参数的变量类型,首先ap向后移动sizeof(t)个单位,指向下一个实际参数的地址,同时返回(ap-sizeof(t))的地址,返回的地址跟刚开始时地址一样。实际上就是为了ap移动到下一个参数的地址,为了下一次输出。

(3)va_end宏的作用

将ap指针赋值为NULL,即0

看一下实现代码:

[cpp] view plain copy
 print?
  1. #include<stdio.h>  
  2. #include<stdlib.h>  
  3. #include<string.h>  
  4. #include<stdarg.h>  
  5.   
  6. void printch(const char ch)   //输出字符  
  7. {    
  8.     putchar(ch);    
  9. }    
  10.   
  11.   
  12. void printint(const int dec)     //输出整型数  
  13. {    
  14.     if(dec == 0)    
  15.     {    
  16.         return;    
  17.     }    
  18.     printint(dec / 10);    
  19.     putchar((char)(dec % 10 + '0'));    
  20. }    
  21.   
  22.   
  23. void printstr(const char *ptr)        //输出字符串  
  24. {    
  25.     while(*ptr)    
  26.     {    
  27.         putchar(*ptr);    
  28.         ptr++;    
  29.     }    
  30. }    
  31.   
  32.   
  33. void printfloat(const float flt)     //输出浮点数,小数点第5位四舍五入  
  34. {    
  35.     int tmpint = (int)flt;    
  36.     int tmpflt = (int)(100000 * (flt - tmpint));    
  37.     if(tmpflt % 10 >= 5)    
  38.     {    
  39.         tmpflt = tmpflt / 10 + 1;    
  40.     }    
  41.     else    
  42.     {    
  43.         tmpflt = tmpflt / 10;    
  44.     }    
  45.     printint(tmpint);    
  46.     putchar('.');    
  47.     printint(tmpflt);    
  48.   
  49. }    
  50.   
  51.   
  52. void my_printf(const char *format,...)    
  53. {    
  54.     va_list ap;    
  55.     va_start(ap,format);     //将ap指向第一个实际参数的地址  
  56.     while(*format)    
  57.     {    
  58.         if(*format != '%')    
  59.         {    
  60.             putchar(*format);    
  61.             format++;    
  62.         }    
  63.         else    
  64.         {    
  65.             format++;    
  66.             switch(*format)    
  67.             {    
  68.                 case 'c':    
  69.                 {    
  70.                     char valch = va_arg(ap,int);  //记录当前实践参数所在地址  
  71.                     printch(valch);    
  72.                     format++;    
  73.                     break;    
  74.                 }    
  75.                 case 'd':    
  76.                 {    
  77.                     int valint = va_arg(ap,int);    
  78.                     printint(valint);    
  79.                     format++;    
  80.                     break;    
  81.                 }    
  82.                 case 's':    
  83.                 {    
  84.                     char *valstr = va_arg(ap,char *);    
  85.                     printstr(valstr);    
  86.                     format++;    
  87.                     break;    
  88.                 }    
  89.                 case 'f':    
  90.                 {    
  91.                     float valflt = va_arg(ap,double);    
  92.                     printfloat(valflt);    
  93.                     format++;    
  94.                     break;    
  95.                 }    
  96.                 default:    
  97.                 {    
  98.                     printch(*format);    
  99.                     format++;    
  100.                 }    
  101.             }      
  102.         }    
  103.     }  
  104.     va_end(ap);           
  105. }    
  106.   
  107.   
  108. int main()    
  109. {    
  110.     char ch = 'A';    
  111.     char *str = "hello world";    
  112.     int dec = 1234;    
  113.     float flt = 1234.45678;     
  114.     my_printf("ch = %c,str = %s,dec = %d,flt = %f\n",ch,str,dec,flt);    
  115.     return 0;    
  116. }   

 运行结果:

ch = A,str = hello world,dec = 1234,flt = 1234.4568





实际上,实现时可以用一个更简单的函数,vprintf函数。

int vprintf(char *format, va_list param);

printf的功能就是用它来实现的,所不同的是,它用一个参数取代了变长参数表,且此参数是通过调用va_start宏进行初始化。其实vprintf也是经过封装的一个函数。
这样就省了我们调用宏对变参函数进行处理,只要开始调用一次va_start宏进行一次初始化即可。


代码如下:

[cpp] view plain copy
 print?
  1. #include<stdio.h>  
  2. #include<stdarg.h>  
  3.   
  4.   
  5. int my_printf(char *str,...)  
  6. {  
  7.     int n;    //记录返回值  
  8.     va_list list;  
  9.     va_start(list,str);  
  10.     n=vprintf(str,list);  
  11.     va_end(list);  
  12.     return n;  
  13. }  
  14.   
  15. int main()  
  16. {  
  17.   
  18.     my_printf("%s,%d","hello world",10);  
  19.   
  20. }  

运行结果:

hello world,10