实现简单的printf函数
来源:互联网 发布:2015淘宝双11销售额 编辑:程序博客网 时间:2024/05/17 23:48
首先,要介绍一下printf实现的原理
printf函数原型如下:
int printf(const char* format,...);
返回值是int,返回输出的字符个数。
例如:
int main(){ int n; n=printf("hello world,%d\n",100); printf("返回值:%d\n",n); return 0;}
测试结果:
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指向下一个参数的储存地址,然后再次分析字符串,直到分析到字符串结尾结束。
通过上面的参数入栈方式我们可以得到如下结论:
如果想将栈中的参数读出来,我们只需要知道,栈顶元素的地址即第一个参数的地址即可。通过前面变参函数的分析,通过变参函数第一个参数可以知道传递的参数个数。
当然,每个参数都有自己的类型,还有的就是字节对齐了。在读取参数的时候,这些问题都必须考虑到。
实际上处理变参时,已经有封装好的宏处理这些所有问题
typedef char * va_list; //将char*别名为va_list;
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
#define va_start(ap,v) (ap = (va_list)&v + _INTSIZEOF(v))
#define va_arg(ap,t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#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
看一下实现代码:
#include<stdio.h>#include<stdlib.h>#include<string.h>#include<stdarg.h>void printch(const char ch) //输出字符{ putchar(ch); } void printint(const int dec) //输出整型数{ if(dec == 0) { return; } printint(dec / 10); putchar((char)(dec % 10 + '0')); } void printstr(const char *ptr) //输出字符串{ while(*ptr) { putchar(*ptr); ptr++; } } void printfloat(const float flt) //输出浮点数,小数点第5位四舍五入{ int tmpint = (int)flt; int tmpflt = (int)(100000 * (flt - tmpint)); if(tmpflt % 10 >= 5) { tmpflt = tmpflt / 10 + 1; } else { tmpflt = tmpflt / 10; } printint(tmpint); putchar('.'); printint(tmpflt); } void my_printf(const char *format,...) { va_list ap; va_start(ap,format); //将ap指向第一个实际参数的地址 while(*format) { if(*format != '%') { putchar(*format); format++; } else { format++; switch(*format) { case 'c': { char valch = va_arg(ap,int); //记录当前实践参数所在地址 printch(valch); format++; break; } case 'd': { int valint = va_arg(ap,int); printint(valint); format++; break; } case 's': { char *valstr = va_arg(ap,char *); printstr(valstr); format++; break; } case 'f': { float valflt = va_arg(ap,double); printfloat(valflt); format++; break; } default: { printch(*format); format++; } } } } va_end(ap); } int main() { char ch = 'A'; char *str = "hello world"; int dec = 1234; float flt = 1234.45678; my_printf("ch = %c,str = %s,dec = %d,flt = %f\n",ch,str,dec,flt); return 0; }
运行结果:
ch = A,str = hello world,dec = 1234,flt = 1234.4568
实际上,实现时可以用一个更简单的函数,vprintf函数。
int vprintf(char *format, va_list param);
printf的功能就是用它来实现的,所不同的是,它用一个参数取代了变长参数表,且此参数是通过调用va_start宏进行初始化。其实vprintf也是经过封装的一个函数。
这样就省了我们调用宏对变参函数进行处理,只要开始调用一次va_start宏进行一次初始化即可。
代码如下:
#include<stdio.h>#include<stdarg.h>int my_printf(char *str,...){ int n; //记录返回值 va_list list; va_start(list,str); n=vprintf(str,list); va_end(list); return n;}int main(){ my_printf("%s,%d","hello world",10);}
hello world,10
- 简单的printf函数实现
- 实现简单的printf函数
- 简单实现printf函数
- printf函数的简单模拟实现
- 自己实现的简单Printf函数
- printf 的简单实现
- Printf()函数简单实现
- 简单模拟实现printf函数
- printf 函数的实现 *
- printf函数的实现
- printf函数的实现
- 不使用man 3 printf的函数,实现一个简单的printf函数
- 可变参数列表:简单printf函数的实现
- printf 函数的实现原理
- 实现自己的printf函数
- printf 函数的实现原理
- 实现自己的printf函数
- printf 函数的实现原理
- JS组件系列——表格组件神器:bootstrap table
- download android source code and build
- 数据库表中数据行去重复
- 一步一步实现iOS QQ第三方登录
- UUID
- 实现简单的printf函数
- JNI函数操作实战
- css中position属性(absolute|relative|static|fixed)概述及应用
- 在Servlet中使用开源fileupload包实现文件上传功能
- MySQL索引原理及慢查询优化
- 题目283 对称排序
- Windows的上下键使sqlplus回退到前面的命令
- Hive2.1.0的 Web Interface配置
- 学问之道无他求其放心而已矣