实现简单的printf函数
首先,要介绍一下printf实现的原理 printf函数原型如下: int printf(const char* format,...); 返回值是int,返回输出的字符个数。 例如: int main() { int n; n=printf(); printf("返回值:%d\n",n); ; } 测试结果: hello world, 返回值: 测试结果是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就可以了。 ()va_start宏的作用 : printf(const char* format,arg1,agr2,....) 实现ap指向第一个实际参数arg1的地址,实际参数指第一个参数format后的第一个参数arg1。即va_start(ap,format)。 ()va_arg宏作用: t指的是分析出来的实际参数的变量类型,首先ap向后移动sizeof(t)个单位,指向下一个实际参数的地址,同时返回(ap-sizeof(t))的地址,返回的地址跟刚开始时地址一样。实际上就是为了ap移动到下一个参数的地址,为了下一次输出。 ()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) //输出整型数 { ) { return; } printint(dec / ); putchar(( + ')); } void printstr(const char *ptr) //输出字符串 { while(*ptr) { putchar(*ptr); ptr++; } } void printfloat(const float flt) //输出浮点数,小数点第5位四舍五入 { int tmpint = (int)flt; * (flt - tmpint)); >= ) { tmpflt = tmpflt / + ; } else { tmpflt = tmpflt / ; } 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"; ; float flt = 1234.45678; my_printf("ch = %c,str = %s,dec = %d,flt = %f\n",ch,str,dec,flt); ; } 运行结果: ch = A,str = hello world,dec = ,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(); } 运行结果: hello world, --------------------- 作者:阿鑫. 来源:CSDN 原文:https://blog.csdn.net/fengxinlinux/article/details/52064816 版权声明:本文为博主原创文章,转载请附上博文链接!
实现简单的printf函数的更多相关文章
- 【C语言】浅谈可变参数与printf函数
一.何谓可变参数 int printf( const char* format, ...); 这是使用过C语言的人所再熟悉不过的printf函数原型,它的参数中就有固定参数format和可变参数(用& ...
- printf 函数的实现原理
/* * ===================================================================================== * * Filen ...
- 你真的很了解printf函数吗?
对C语言中经常使用的printf这个库函数,你是否真的吃透了呢? 系统化的学习C语言程序设计,是不是看过一两本C语言方面的经典著作就足够了呢?答案是显而易见的:不够.通过这种典型的入门级的学习方式,是 ...
- 可变参数列表与printf()函数的实现
问题 当我们刚开始学习C语言的时候,就接触到printf()函数,可是当时"道行"不深或许不够细心留意,又或者我们理所当然地认为库函数规定这样就是这样,没有发现这个函数与普通的函数 ...
- C 中 关于printf 函数中度剖析
题外话 这篇博文主要围绕printf函数分析的,主要讲解printf 使用C的可变参数机制, printf是否可重入(是否线程安全), printf函数的源码实现. 正文 1.C中可变参数机制 我们 ...
- 三,对于printf函数和C语言编程的初步拓展
前面说过了,任何程序都要有输出,所以printf函数是一个很重要的函数,所以有必要在学变量之前先拓展一下. 其实编程就是用计算机语言说话,一句一句地说,只要语法没错就能运行,至于能实现什么功能,就看编 ...
- 研究不定数量参数的函数并实现一个printf函数
一.前提知识 1.如何传递参数(主函数) a.函数的参数是通过栈传递,而且是从右到左依次入栈 b.即使是char型变量,在传递参数时,也是占用两个字节,因为push操作是两个字节为单位的. c.sho ...
- 在keil中使用printf()函数的要点
在keil中printf默认是向串口中发送数据的,所以,如果应用该函数,必须先初始化串口,否则可能引起死机的情况,并且在printf之前应该先将TI置位,摘抄原因如下: 1.printf函数是调用pu ...
- 【stm32】实现STM32的串口数据发送和printf函数重定向
在调试电机驱动程序的时候,是不能随便利用中断来进行一些寄存器或数据的查看的,不然你在运行的时候突然来一下,如果占空比大的话那可能直接就把MOS管给烧了,所以我们很多情况下只能使用USART(串口)来进 ...
随机推荐
- 【Axios】前端页面使用axios调用后台接口
项目基本情况 前端项目是用vue.js做的,前端起的服务URL:http://localhost:8080/ 后端项目是用Node.js做的,后端起的服务URL:http://localhost:30 ...
- Spring基础系列-参数校验
原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9953744.html Spring中使用参数校验 概述 JSR 303中提出了Bea ...
- Java——重载和重写
前言 在程序设计中经常会遇到对对方法的重载或者重写,下面将介绍重载和重写. 重载(Overloade) 重载出现的原因 任何程序设计语言都具备的一项重要特性就是对名字的运用.当创建一个对象时,就给对象 ...
- cocos creator主程入门教程(一)—— 初识creator
五邑隐侠,本名关健昌,10年游戏生涯,现隐居五邑.本系列文章以TypeScript为介绍语言. 我们在cocos creator新建一个Hello TypeScript项目,都会有一个assets/S ...
- 【前端】js中数组对象根据内容查找符合的第一个对象
今天在写一个混合开发版的app,其中一个功能是扫描快递单号,客户要求不能扫描重复的快递单号!所有就验证查出. 首先实现思路就是: 1.定义一个全局数组变量:var nubList = []; 2.进入 ...
- Java基础:Object类中的equals与hashCode方法
前言 这个系列的文章主要用来记录我在学习和复习Java基础知识的过程中遇到的一些有趣好玩的知识点,希望大家也喜欢. 一切皆对象 对于软件工程来说面向对象编程有一套完整的解决方案:OOA.OOD.O ...
- 阿里巴巴(alibaba)系列_druid 数据库连接池_监控(一篇搞定)记录执行慢的sql语句
参考帖子:http://www.cnblogs.com/han-1034683568/p/6730869.html Druid数据连接池简介 Druid是Java语言中最好的数据库连接池.Druid能 ...
- C++系列总结——mutable关键字
介绍 mutable的中文意思是易变的,是C++的一个关键字.它的作用就是允许修改被const修饰的对象的成员变量. 常用场景 什么情况下我们会使用到mutable? 一般我们会用const修饰get ...
- ios端的Safari浏览器中,输入框加入readonly之后,点击还能获取焦点的解决办法。
事情的起因是,新增一个需求,原来的输入框点击不要出现系统自带的键盘,出现我们模拟的键盘.如果是一次性开发的话, 我肯定把这个输入框写成一个div或者其他的元素,然后点击之后出现我们的模拟键盘,这样就不 ...
- 代码编辑器横评:为什么 VS Code 能拔得头筹
摘要: 为什么 VS Code 这么火... 公众号:玩转 VS Code 原文:代码编辑器横评:为什么 VS Code 能拔得头筹 Fundebug经授权转载,版权归原作者所有. 2015 年 4 ...