用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_list): 可变参数列表,指向当前正要处理的可变参数  * 输入参数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;              //定义一个可变 参数的(字符指针) 
     ​int val,r_val;
     char count,ch;​
     char *s = NULL;​
     int res = 0;             //返回值

     va_start(ap,str);        //初始化ap
     while('\0' != *str)//str为字符串,它的最后一个字符肯定是'\0'(字符串的结束符)
     { 
          switch(*str)
          {
              case '%':	     //发送参数
              str++;
              ​switch(*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;​ 
                 ​break;
                default :;
             }​
             break;
          case '\n':
               ​m_putchar('\n'); 
               res += 1;​
               break;​
          case '\r':
               m_putchar('\r'); 
               res += 1;​
               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 )
​​{
      ​unsigned long sum = 1;
      while(y--)
      {
           sum *= x;​
      }​
      return sum;​
}​
//打印字符​
void m_putchar(const char ch)
{
       //这部分底层一般由自己实现,下面的这两行是我自己在STM32串口上的实现
      ​while(RESET == USART_GetFlagStatus(USART1,USART_FLAG_TC));
      USART_SendData(USART1,ch);
}

//打印字符串
void m_pustr(const char *str)
{
      while(*str)
      {
            m_putchar(*str++);
      }​
}​

好了,大概就这样,同时也希望大家和我一块学习讨论提高。
 

 

 

 

 

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页