⽤C语⾔写⾃⼰的printf函数第⼀部分分析
⾸先看⼀下printf函数的原型声明:
int __cdecl printf(const char * __restrict__ _Format,...);
上⾯这个是我在Dev-C++上复制过来的函数声明,其实⽆论是哪个版本声明部分作⽤都是⼀样的。
接⼀下我们看⼀下printf的返回值是什么玩意?
上图就是我⾃⼰实际测试得出来的⼀个结果。
6621! 66⼤家都能想的来,是我要求打印的值,那21⼜是什么玩意啊,接⼀下看下图
第⼀⾏输出51, 5就是要输出的数字,那printf的返回值1⼜是什么啊?
那看第⼆⾏112,11是我们要输出的数字,那printf的返回值2是什么啊?
结合上⾯两个例⼦,我想⼤家已经猜到了,printf的返回值是要输出到显⽰屏的字符的个数。
那么就再举⼀个例⼦证明⼀下上⾯的猜测:
第⼀⾏是我们要输出的16进制的数字,总共10个字符(包括8个数字和0x这两个字符组成的提⽰符)
那如果按刚才所说的,第⼆⾏输出的printf的返回值就应该为10了,怎么回事11昵?
仔细看,内部那个printf要输出的字符串的最后⼀个字符是’\n’,这个叫做转义字符,它的功能是换⾏。也就是所从第⼀⾏跳到第⼆⾏,从图中也能看到11在第⼆⾏。虽然转义字符不能打印,但也属于字符,所以也占⼀个字节,所以总共11个字符。
那么刚开始的那个问题也应该迎刃⽽解了吧!
再看⼀下
__cdecl , __restrict__ (C99新增的关键字)
这两个是什么玩意,我百度了⼀下
__cdecl 是C Declaration的缩写(declaration,声明),表⽰C语⾔默认的函数调⽤⽅法:所有参数从右到左依次⼊栈,这些参数由调⽤者清除,称为⼿动清栈。
被调⽤函数
不会要求调⽤者传递多少参数,调⽤者传递过多或者过少的参数,甚⾄完全不同的参数都不会产⽣编译阶段的错误。
关键字restrict通过允许编译器优化某⼏种代码增强了计算⽀持。它只可⽤于指针,并表明指针是访问⼀个数据对象的惟⼀且初始的⽅式。(详细使⽤请百度⼀下就知道)
具体来说,这两个关键字只是起到修饰作⽤。没有这连个对程序的运⾏也不会有什么影响。
现在分析函数的参数:
const char * _Format, ...
第⼀个参数为(_Format)字符指针,const 意味着在这个函数⾥不能修改这个字符串⾥的内容(常字符串),第⼆个参数为(...)。
第⼀个参数字符串,不⽤多说。。。
第⼆个参数...,表⽰参数个数没有限制。
不定参数分析:
说到这⾥,我们就必须说⼀下⼀个标准库必须有的头⽂件了:
stdarg.h
这个头⽂件⾥定义了这么⼏个重要的宏!
va_list
va_start(ap, param)
va_arg(ap, type)
va_end(ap)
va_copy(dest, src)
typedef char *va_list;
#define _AUPBND (sizeof (acpi_native_int) - 1)
#define _ADNBND (sizeof (acpi_native_int) - 1)
#define _bnd(X, bnd) (((sizeof (X)) + (bnd)) & (~(bnd)))
#define va_arg(ap, T) (*((T*) (((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))
#define va_end(ap) (void) 0
#define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))
以上的⼀些代码是我在linux源代码⾥到定义的⼀些宏。
va_list 的是⼀个字符指针,可以理解为指向当前参数的⼀个指针,取参必须通过这个指针进⾏。
va_start(ap,v)对ap 进⾏初始化,⽤于获取参数列表中可变参数的⾸指针
ap⽤于保存函数参数列表中可变参数的⾸指针(即,可变参数列表)
A为函数参数列表中最后⼀个固定参数
va_arg⽤于获取当前ap所指的可变参数并将并将ap指针移向下⼀可变参数 * 输⼊参数ap(类型为va_lis
t): 可变参数列表,指向当前正要处理的可变参数 * 输⼊参数T: 正要处理的可变参数的类型 * 返回值: 当前可变参数的值
va_end⽤于结束对可变参数的处理。
实际上,va_end被定义为空.它只是为实现与va_start配对(实现代码对称和"代码⾃注释"功能)
当然这⾥对⼏个宏的内容就不多做介绍了,后⾯闲了再对具体做解释,⼤家先知道他们的作⽤,等⼀下我们下⾯会遇到。
第⼆部分写函数
接下来说⼀下为什么要⾃⼰写printf函数,为什么不⽤系统的printf函数
1. 标准库的库函数都不开源,也就是看不到代码,不好调试
2. 标准库的printf⽐较复杂,代码量提升了,⽽且速度⽐较慢,局限性⼤
第⼀个原因就不解释了
第⼆个说⼀下,标准库的printf只能在pc机上⽤⽐如⽤在没有显⽰器的东西上就不能⽤了,特别是在⼩
型嵌⼊式系统或单⽚机上,本⾝ROM就⼩,⽽且速度⽐较慢(⼏M--⼏⼗M的CPU)。特别是⼤多处情况下浮点型并不需要输出,⽽标准库的printf浮点运算占了很⼤⼀部分,这部分很⼤程度上降低了CPU对⽓它程序的计算,所以说,在⼩型系统中,写⼀个⾃⼰的printf的是⾄关重要的。
说了这么多,那就开始开⼲吧!
int m_printf(const char *str,...)
{
va_list ap; //定义⼀个可变参数的(字符指针)
i nt val,r_val;
char count,ch;
char *s = NULL;
int res = 0; //返回值
va_start(ap,str); //初始化ap
while('\0' != *str)//str为字符串,它的最后⼀个字符肯定是'\0'(字符串的结束符)
{
switch(*str)
{
{自定义函数怎么用c语言
case '%': //发送参数
str++;
s witch(*str)
{
case 'd': //10进制输出
val = va_arg(ap, int);
r_val = val;
count = 0;
while(r_val)
{
count++; //整数的长度
r_val /= 10;
}
res += count; //返回值长度增加
r_val = val;
while(count)
{
ch = r_val / m_pow(10,count - 1);
r_val %= m_pw(10,count - 1);
m_putchar(ch + '0'); //数字到字符的转换
count--;
}
break;
case 'x': //16进制输出
val = va_arg(ap, int);
r_val = val;
count = 0;
while(r_val)
{
count++; //整数的长度
r_val /= 16;
}
res += count; //返回值长度增加
r_val = val;
while(count)
{
ch = r_val / m_pow(16, count - 1);
r_val %= m_pw(16, count - 1);
if(ch <= 9)
m_putchar(ch + '0'); //数字到字符的转换
else
m_putchar(ch - 10 + 'a');
count--;
}
break;
case: 's': //发送字符串
s = va_arg(a, char *);
m_putstr(s); //字符串,返回值为字符指针
res += strlen(s); //返回值长度增加
break;
case 'c'
m_putchar( (char)va_arg(ap, int )); //⼤家猜为什么不写char,⽽要写int res += 1;
b reak;
default :;
}
break;
case '\n':
m_putchar('\n');
res += 1;
break;
case '\r':
m_putchar('\r');
res += 1;
break;
break;
defaut : //显⽰原来的第⼀个参数的字符串(不是..⾥的参数o)
m_putchar(*str);
res += 1;
}
str++;
}
va_end(ap);
return res;
}
上⾯⽤到了⼀些函数,⼤概说⼀下 pow 就是 x ^y
static unsigned long m_pow(int x,int )
{
u nsigned long sum = 1;
while(y--)
{
sum *= x;
}
return sum;
}
//打印字符
void m_putchar(const char ch)
{
//这部分底层⼀般由⾃⼰实现,下⾯的这两⾏是我⾃⼰在STM32串⼝上的实现w hile(RESET == USART_GetFlagStatus(USART1,USART_FLAG_TC));
USART_SendData(USART1,ch);
}
//打印字符串
void m_pustr(const char *str)
{
while(*str)
{
m_putchar(*str++);
}
}
好了,⼤概就这样,同时也希望⼤家和我⼀块学习讨论提⾼。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论