当前位置:网站首页>自己实现printf函数
自己实现printf函数
2020-11-09 15:25:00 【shzwork】
origin:https://www.e-learn.cn/content/qita/716071
首先我们先来了解一下基础知识。
printf格式字符如下所示,
格式字符 |
说明 |
d |
以带符号的十进制形式输出整数(整数不输出符号) |
u |
以无符号十进制形式输出整数 |
x |
以十六进制无符号形式输出整数(不输出前导符0x), 用x则输出十六进制数的a~f时以小写形式输出 |
c |
以字符形式输出,只输出一个字符 |
s |
输出字符串 |
在C语言中,不仅参数的类型可变,而且参数的个数也是可变的.也就是说,在形参表中可以不明确指定传递参数的个数和类型,以上所说的printf函数就是如此.这种函数称之为变参函数。可变长参数函数的参数数目和类型虽然是可变,但其设计原理与固定参数函数的设计原理是一致的,我们有办法告诉变参函数没有指定的参数的个数和类型。
printf的声明如下:
int printf(const char *format, ...);
format:固定参数
... :可变参数(变参)
在C语言中,变参函数的声明是放在atdarg.h标准库中的,当然可以直接包含进来使用它,但我们这里自己定义它(参照头文件的宏定义)。
typedef char * va_list;
/* 当sizeof(n)=1/2/4时,_INTSIZEOF(n)=4 */
#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_arg(ap,t) (ap = ap + _INTSIZEOF(t), *(t *)(ap - _INTSIZEOF(t)))
/* 下面这个话的意思是,先指向下一个变量的地址,然后再(减)回来,最后取它原来地址里面的值 */
#define va_arg(ap,t) (*(t *)(ap = ap + _INTSIZEOF(t), ap - _INTSIZEOF(t)))
/*指针用完后指向0地址,防止野指针的出现*/
#define va_end(ap) ( ap = (va_list)0 )
这里需要注意一下#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) -1) & ~(sizeof(int) - 1) ),可能不是很好理解,下面补充一个知识点:由于在x86(32位机器)平台下,GCC编译器默认按4字节对齐,所以当sizeof(n)=1/2/4时,_INTSIZEOF(n)=4这句话的意思是当变量类型是char,unsigned int,int,那么不足4字节的都按照4字节补齐。
下面给出My_printf.c
#include "my_printf.h"
//==================================================================================================
typedef 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_arg(ap,t) ( *(t *)( ap=ap + _INTSIZEOF(t), ap- _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 ) //这里就不解释了,不难理解
//==================================================================================================
unsigned char hex_tab[]={'0','1','2','3','4','5','6','7',\
'8','9','a','b','c','d','e','f'}; //输出各种进制下的字符
static int outc(int c)
{
__out_putchar(c); //这里的_out_putchar其实就是putchar,在.h中定义
return 0;
}
static int outs (const char *s) //输出字符串
{
while (*s != '\0')
__out_putchar(*s++);
return 0;
}
static int out_num(long n, int base,char lead,int maxwidth)
{
unsigned long m=0;
char buf[MAX_NUMBER_BYTES], *s = buf + sizeof(buf); // sizeof算结束符'\0' ,strlen不算
int count=0,i=0; //注意这里s指向buf的末端,至于为什么继续往下看
*--s = '\0'; //先--,在赋值结束符,因为sizeof算结束符在内的长度
if (n < 0){
m = -n; //如果是输出的是负数就取反
}
else{
m = n;
}
do{
*--s = hex_tab[m%base];
count++;
}while ((m /= base) != 0); //将要打印的数字从个位开始一位一位存储在数组buf中,如果上面不是指向buf末端,
if (n < 0)
*--s = '-'; //负数的话加负号
return outs(s);
}
/*reference : int vprintf(const char *format, va_list ap); */
static int my_vprintf(const char *fmt, va_list ap)
{
char lead=' ';
int maxwidth=0;
for(; *fmt != '\0'; fmt++)
{
if (*fmt != '%') { //顺序查找判断,遇到%就推出,否则继续循环输出
outc(*fmt);
continue;
}
fmt++;
if(*fmt == '0'){ //遇到‘0’说明前导码是0
lead = '0';
fmt++;
}
while(*fmt >= '0' && *fmt <= '9'){ //紧接着的数字是长度,算出指定长度
maxwidth *=10;
maxwidth += (*fmt - '0');
fmt++;
}
switch (*fmt) { //判断格式输出
case 'd': out_num(va_arg(ap, int), 10,lead,maxwidth); break;
case 'o': out_num(va_arg(ap, unsigned int), 8,lead,maxwidth); break;
case 'u': out_num(va_arg(ap, unsigned int), 10,lead,maxwidth); break;
case 'x': out_num(va_arg(ap, unsigned int), 16,lead,maxwidth); break;
case 'c': outc(va_arg(ap, int )); break;
case 's': outs(va_arg(ap, char *)); break;
default:
outc(*fmt);
break;
}
}
return 0;
}
//reference : int printf(const char *format, ...);
int printf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
my_vprintf(fmt, ap);
va_end(ap);
return 0;
}
int my_printf_test(void)
{
printf("My_printf test\n\r") ;
printf("test char =%c,%c\n\r", 'A','a') ;
printf("test decimal number =%d\n\r", 123456) ;
printf("test decimal number =%d\n\r", -123456) ;
printf("test hex number =0x%x\n\r", 0x55aa55aa) ;
printf("test string =%s\n\r", "yoyoyo") ;
printf("num=%08d\n\r", 12345);
printf("num=%8d\n\r", 12345);
printf("num=0x%08x\n\r", 0x12345);
printf("num=0x%8x\n\r", 0x12345);
printf("num=0x%02x\n\r", 0x1);
printf("num=0x%2x\n\r", 0x1);
printf("num=%05d\n\r", 0x1);
printf("num=%5d\n\r", 0x1);
return 0;
}
实验结果如下:
版权声明
本文为[shzwork]所创,转载请带上原文链接,感谢
https://my.oschina.net/u/4000302/blog/4710035
边栏推荐
- cad教程 cad2016安装教程
- 融云完成数亿人民币 D 轮融资,将持续打造全球云通信能力
- 实在是太棒了!阿里资深架构师20年经验整理分享ServiceMesh实战文档,涨薪就差这篇文章了!
- 移动安全加固助力 App 实现全面、有效的安全防护
- Lazy to write a document, swagger document does not smell
- SEO建设者,有哪些说不出的苦?
- 百万年薪架构师之路:谈应用系统架构设计
- Native地图与Web融合技术的应用与实践
- What kind of experience does a doctor have when he turns his secret love brother into a husband?
- How to download and install autocad2020 in Chinese
猜你喜欢
A certification and authorization solution based on. Net core - hulutong 1.0 open source
超大折扣力度,云服务器 88 元秒杀
嘉宾专访|2020 PostgreSQL亚洲大会阿里云数据库专场:王健
Lazy to write a document, swagger document does not smell
Two ways for Tencent cloud server to build WordPress website
I heard that you changed your registered residence overnight. How can you help yourself if you work like ping?
What is website [new four modernizations]?
你的钱为什么会被转走,这篇文章告诉你答案
Embedded assembly in IOS
The way of a million year salary Architect: on the architecture design of application system
随机推荐
CCF BDCI热门赛题:非结构化商业文本信息中隐私信息识别
详解三种不同的身份验证协议
What kind of experience does a doctor have when he turns his secret love brother into a husband?
python中常见的一些错误异常类型
H5公众号点击内置浏览器的关闭(左上角的叉叉)监听到事件
毕业一年后接私活赚了10w,还拿了几家大厂offer!
A quick start to Shell Scripting
听说你一夜之间变了户籍,依萍如洗的打工人该如何自救?
CAD2016下载AutoCAD2016下载安装详细教程CAD下载
.NET报表生成器Stimulsoft Reports.Net 发布最新版v2020.5!
设置背景图片的两种方式,并解决手机端背景图片高度自适应问题
Notes for csp-j / s 2020
Interview series 2: concurrent programming
CAD tutorial cad2016 installation course
Put method of immutablemap
Hadoop学习(三)-YARN
Booker · apachecn programming / back end / big data / AI learning resources 2020.11
CCF BDCI hot topic: privacy information recognition in unstructured business text information
Native地图与Web融合技术的应用与实践
MES system is different from traditional management in industry application