STM32的USARTx中u3_printf函数解析

STM32的USARTx中u3_printf函数解析

我们在STM32中常常使用printf来将信息打印至PC端控制台上。但是有这样一种函数,它可以把数据以我们指定的格式装进字符串中——sprinft和vsprintf。

⑴ sprintf函数使用的方式如下:

① 将多个参数以指定格式写入字符串

int a=1,b=2;  
char s[10];  
sprintf(s,"a=%d,b=%d",1,2);  
puts(s); 

 

输出结果:a=1,b=2

② 错误使用:在函数封装中使用

void Myprintf(const char* fmt,...)  // 传递个数不定的参数
{  
  char s[10];  
  sprintf(s,fmt);  // 出现错误
  puts(s);  
}  

 

封装函数的使用:

int a=1,b=2;  
Myprintf("a=%d,b=%d",a,b);  

 

输出结果:

a=?,b=? // 不确定值

 

这是因为可变参数在入栈时如下列方式排列:

Param1

Param2

……

Paramx

但是sprintf只接收了Param1作为自己的参数,这样的话,我们调用的封装函数等价如下:

int a=1,b=2;  
char s[10];  
sprintf(s,"a=%d,b=%d");  // 这里的a,b根本没有值
puts(s);

 

⑵ vsprintf粉墨登场了,它解决了sprintf无法读取可变参数的缺陷

我们要知道可变参数在栈中的排列是连续的,而且占用了一段栈的内存空间,我们首先要知道栈区中可变形参的排列顺序:

int func(int num,...)
{
     ......
}

 

其对应可变参数在栈区中的排列顺序是:

参数名称

num

参数1

参数N

栈中顺序

N+1

N

1

这里要注意:入栈的顺序是“先入后出-FILO”,因此这里的元素在栈中是倒序排列的,即栈区的末端地址就等于可变参数列表中首个参数的地址。

这里我们要将不定个数的参数传递给函数vsprintf函数,我们就必须要借助这N个参数(参数N~参数1)的地址来进行传参。我们一定要清楚:可变参数指的是参数1~N不包括第一个参数num,这个参数是已知的不算可变参数。

函数vsprintf原型如下:

// 函数功能:将函数地址->ParamEndAddr地址之间的所有参数以Format指定的格式转化为字符串进而赋给以StringFirstAddr为首地址的字符数组
// ParamEndAddr:参数列表中首个参数的地址(栈区中可变参数列表的末端地址)
// Format:指定转化为字符串的格式(详见:printf函数打印字符串的格式)
// StringFirstAddr:用于接受转换后字符串的字符型数组首地址
vpsrintf(char* StringFirstAddr, Format, ParamEndAddr);

 

这里我们要注意:函数在内存中的分布分为RAM和FLASH两大块,其中参数存在RAM中函数的执行内容存在FLASH中,这里的函数名就是参数列表所在栈区在RAM中的首地址,当我们提供一个RAM中参数列表所在栈区的末端地址,我们就可以将整个可变参数列表提取出来。

这里我们介绍几个函数:

参数名称

fmt

参数1

参数N

栈中顺序

N+1

N

1

参数类型

char*

int

int

程序如下:

void func(char *fmt, ...)
{
     va_list ap; // 指向参数列表(栈区)中某个元素的指针类型

     va_start(ap, fmt);
     va_arg(ap, int);
     va_end(va);
}

 

① 确定“栈区末端地址”的函数——va_start()

函数原型:va_start(va_list ap, 参数列表中的第一个元素)

函数功能:va_start(va_list ap, char* fmt)

参数名称

fmt

参数1

参数N

栈中顺序

N+1

N

1

  

ap指向的对象

  

函数的调用使得ap指针指向可变参数列表中的首个元素也是栈区中的最后一个元素地址。

② 根据栈区末端地址和参数类型提取可变参数列表中的指定元素——va_arg()

函数原型:va_arg(va_list ap, 元素的数据类型)

函数功能:va_arg(va_list ap, int)

参数名称

fmt

参数1

参数N

栈中顺序

N+1

N

1

参数类型

char*

int

int

  

ap指向的对象

  

va_arg()所做的就是根据ap指向的地址,和第二个参数所确定的类型,将这个参数的中的数据提取出来,作为返回值,同时让ap指向下一个参数。

③ 将指针置空(NULL)的函数——va_end()

函数原型:va_end(va_list ap)

函数功能:va_end()所做的就是让ap这个指针指向0。

函数vsprintf使用方式如下:

void Myprintf(const char* fmt,...)  
{  
  char s[10];  
  va_list ap;  // 定义指向栈区某个元素的指针
  va_start(ap,fmt);  // ap指向可变参数列表的首地址(栈区的末端元素地址)
  vsprintf(s,fmt,ap);  // 将函数首地址~ap之间的可变形参列表赋值给vsprintf函数形参列表
  va_end(ap); // ap指针置空(NULL)
  puts(s);  // 打印字符串至屏幕上
}  

 

但是,这里将一段封装函数Myprintf的参数列表赋值给vsprintf函数当作参数,如果我们操作单个可变参数列表中的元素,我们该如何做呢?

相较于前面一段一段的使用参数列表的元素的最大不同在于“我们需要指定输入参数的个数”,因为我们输入的参数的数据类型未知,个数未知,因此我们要知道我们每个输入参数的数据类型和输入参数的总个数才可以。

// 这里我们指定了可变形参个数为num个
float Average(int num,...) 
{
       int i=0;
       float sum;
       va_list valist;
       va_start(valist,num); // 通过参数列表中收个元素(int num)找到可变参数列表中的收个元素地址并赋值给valist
       for (i = 0; i < num; i++)
       {
             // 根据输入元素的数据类型在栈区中读取数据
             sum+=(float)va_arg(valist,int); // 假设所有输入元素全文int类型的参数,当不断循环时不断改变valist指针指向元素,进而不断轮询访问参数列表中的所有元素
       }
       va_end(valist); // 置空(NULL)valist指针
       return sum/(float)num;
}

 

注意:我们要使用这些函数必须要加载“#include <stdarg.h>”头文件。

 

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肥肥胖胖是太阳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值