printf是c语言下非常常用的一个函数,几乎每个程序员在一开始第一个学的函数应该就是printf吧!
今天来实现一个printf函数,虽然printf是我们常用的函数,但实现起来其实还是蛮考基础的!
下面一步一步带大家实现:
一. 编写一个入口函数并导入标准io头文件
#include<stdin.h>
int main(){
}
二. printf函数准备工作
前言:
printf函数的第一个参数是格式字符,printf通过分析格式字符来判断该打印怎样格式的数据,我们需要打印不同类型格式的话需要进行不同的操作。
所以我们需要申请很多函数用来打印不同数据类型,这里我们使用标准c的putchar函数实现,注意printf内部也是调用系统内核里的io函数,但是这里我们不是做内核开发所以不涉及,这里我们使用putchar函数来实现。
putchar函数每次只能打印一个字符,这里我们自己编写格式分析,然后通过分析来打印,这里会涉及到递归,整数到类型转换。
函数实现:
一.这里先实现int类型的打印
实现思路:
1.利用逆序递归逆序处理工作
2.利用字符编码差来将整数转换成字符
3.为了保证字符太大不会大于字符ascii码最大值用%取余运算来保证每次只处理一个整数
4.每次/10保证整数每次能递减一个数
//int
void PrintD(int Data){
//判断Data是否小于等于0保证递归栈尾不溢出
if(Data<=0){
return;
}
//逆递归,逆序处理
PrintD(Data/10);
//先Data取余,123/10余3
//然后在+’0’转换成字符的ascii码
// 因为字符1的ascii码是49,而0的ascii码是48,比如Data是自然数1那么1%10=1,1+48=字符49(后面来解释一下自然数与ascii码的关系)
putchar(Data%10+’0’);//整数转字符
//注意由于是逆递归,字符打印会被逆序打印出来(如果不懂什么是逆递归我后面会写一篇博客介绍)
}
二.实现float类型的打印
实现思路:
1.先取出整数部分
2.在取出小数部分
3.在使用打印int方式来打印取出的数值
三. 实现double类型
实现思路:
和实现float一样,只是double精度是8位(看位编译器)改一下精度就好了
只需要复制上面的代码就好了!
四.实现char和char*
实现思路:
这个就非常简单了,char只需要直接putchar就可以了,char*只需要判断是否遇到\0就好了!
//char
void PrintC(char C){
putchar(C);
}
//char*
void PrintStr(const char* Str){
while(*Str!=‘\0’){
putchar(Str++);
}
}
//*对char*解引用指向第一个char首地址所以取第一个字符然后对指针偏移递增即可(编译器会根据指针类型大小来递增不同的自然数,比如int递增4,char递增1)
四. 进制打印
实现思路:
定义一个字符:
123456789abcdef
然后通过表达式取制定进制权值的余即可(后面详细介绍这个表达式)
比如123
123%16=11 选择b
123/16=7
7%16=7选择7
也就是等于7b
这是c语言里的一个进制字符转换算法公式
//0x
void PrintX(unsigned long Num,int Base){
//判断递归是否小于0
if(Num<=0){
return;
}
//逆序递归
PrintX(Num/Base,Base);
//取权值余,这是自然数转进制字符的一种表达式
putchar("0123456789abcdef"[Num%Base]);
}
六. 实现Printf
实现思路:
通过判断字符来分析格式字符!
//Printf
int My_Printf(char* ForMat,...){
int num = 0; //打印字符数量
//可变参数列表
va_list va_l;
va_start(va_l,ForMat);
//获取首字符
char Line = *ForMat;
while(Line!='\0'){//终结字符
if(Line == '%'){//判断是否遇到格式符号
Line = *ForMat++;//遇到的话递增判断后面的字符是什么
switch(Line){ //校验
case 'c': //char
PrintC(va_arg(va_l,char)); //传递参数
break;
case 's'://str
PrintStr(va_arg(va_l,char*));
break;
case 'f'://float
PrintF(va_arg(va_l,float));
break;
case 'lf'://double
PrintLF(va_arg(va_l,double));
break;
case 'p'://地址ptr,地址是多种类型的,所以使用void全能类型(c语言到时候会根据数据类型编号隐转换)
PrintP(va_arg(va_l,void*));
break;
case 'o'://打印八进制
PrintX(va_arg(va_l,int),8);
break;
case 'ox'://打印十六进制
PrintX(va_arg(va_l,int),16);
break;
default://如果不是任何一个格式,则直接打印
putchar('%');
putchar(Line);
break;
}
}
putchar(Line);
Line = *ForMat++;
++num;
}
return num;
}
完整代码:
#include<stdin.h>
#include<stdarg.h>
//int
void PrintD(int Data){
//判断Data是否小于等于0保证递归栈尾不溢出
if(Data<=0){
return;
}
//逆递归,逆序处理
PrintD(Data/10);
//先Data取余,123/10余3
//然后在+’0’转换成字符的ascii码
// 因为字符1的ascii码是49,而0的ascii码是48,比如Data是自然数1那么1%10=1,1+48=字符49(后面来解释一下自然数与ascii码的关系)
putchar(Data%10+’0’);//整数转字符
//注意由于是逆递归,字符打印会被逆序打印出来(如果不懂什么是逆递归我后面会写一篇博客介绍)
}
//float
void PrintF(float Data){
//取出整数部分
int I_Data = (int)Data;
//float精度是6位(这里说的是32位编译器),首先先将整数部分去掉
Data -= I_Data;
//取精度
int Flo = 1000000*Data;
//打印整数
PrintD(I_Data);
//分割符
putchar(‘.’);
//小数部分
PrintD(Flo);
//其实思路很简单,就是取出小数变成整数然后打印就行了!
}
//double
void PrintLF(float Data){
//取出整数部分
int I_Data = (int)Data;
//float精度是6位(这里说的是32位编译器),首先先将整数部分去掉
Data -= I_Data;
//取精度
int Flo = 100000000*Data;
//打印整数
PrintD(I_Data);
//分割符
putchar(‘.’);
//小数部分
PrintD(Flo);
//其实思路很简单,就是取出小数变成整数然后打印就行了!
}
//char
void PrintC(char C){
putchar(C);
}
//char*
void PrintStr(const char* Str){
while(*Str!=‘\0’){
putchar(Str++);
}
}
//0x
void PrintX(unsigned long Num,int Base){
//判断递归是否小于0
if(Num<=0){
return;
}
//逆序递归
PrintX(Num/Base,Base);
//取权值余,这是自然数转进制字符的一种表达式
putchar("0123456789abcdef"[Num%Base]);
}
void PrintP(unsigned long Num){
if(Num<=0){
return;
}
PrintX(Num,16);
}
//Printf
int My_Printf(char* ForMat,...){
int num = 0; //打印字符数量
//可变参数列表
va_list va_l;
va_start(va_l,ForMat);
//获取首字符
char Line = *ForMat;
while(Line!='\0'){//终结字符
if(Line == '%'){//判断是否遇到格式符号
Line = *ForMat++;//遇到的话递增判断后面的字符是什么
switch(Line){ //校验
case 'c': //char
PrintC(va_arg(va_l,char)); //传递参数
break;
case 's'://str
PrintStr(va_arg(va_l,char*));
break;
case 'f'://float
PrintF(va_arg(va_l,float));
break;
case 'lf'://double
PrintLF(va_arg(va_l,double));
break;
case 'p'://地址ptr,地址是多种类型的,所以使用void全能类型(c语言到时候会根据数据类型编号隐转换)
PrintP(va_arg(va_l,void*));
break;
case 'o'://打印八进制
PrintX(va_arg(va_l,int),8);
break;
case 'ox'://打印十六进制
PrintX(va_arg(va_l,int),16);
break;
default://如果不是任何一个格式,则直接打印
putchar('%');
putchar(Line);
break;
}
}
putchar(Line);
Line = *ForMat++;
++num;
}
return num;
}
int main(){
My_Printf("%d%c%s%f%lf",1,'d',"hello word",0.12,0.34567);
return 0;
}