如何编写一个可变参数函数?如何让所有单片机的所有串口实现printf函数?

这篇具有很好参考价值的文章主要介绍了如何编写一个可变参数函数?如何让所有单片机的所有串口实现printf函数?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

(1)由于真的复习不下去,就想着写一篇博客拉回自己的心思。于是想到了长期有疑惑,但是一直没有进行深入了解的C语言可变参数函数。
(2)本人查阅了一些网上的资料,以及自己的理解写出来了这一片博客。首先再次感谢肯哥的答疑。
(3)借鉴文章:
C51单片机中如何实现printf输出log?;
C语言中的可变参数函数;
MSP430F5529 DriverLib 库函数学习笔记(四点五)printf打印输出;

什么是可变参数

(1)首先,我们需要知道什么是可变参数。在我们日常编写程序的时候,所有的参数都是指定的个数的。如下

void Add(int a,int b);

(2)但是我们有没有思考过一个问题,为什么printf()可以传入多个参数?

printf("hello world");
printf("hello %s","world");
printf("%s %s","hello","world");
printf("%d + %d = %d",2,3,5);

(3)这个时候我们就有了解一下printf()函数的定义了。我们查看printf()函数的定义如下,发现他的第二个参数是"…“,那么这个”…“是什么呢?这个”…"就说明了,这个函数是一个可变参数函数。

int printf(char *fmt, ...);

如何编写一个可变参数函数

了解三个宏

(1)在编写可变参数函数之前,我们是需要知道三个宏的。关于这三个宏的介绍,我上面给出的链接已经有非常专业的解释了。如果想知道他的专业解释,请看上面给出的链接。我这里只会使用非常粗俗的白话解释着三个宏的作用。
(2)
<1>首先,如果我们在一个函数中,需要开始处理可变参数。那么开头就需要调用va_start()这个宏,结束使用可变参数的时候,就使用va_end()这个宏。
<2>这个过程有点类似动态内存管理,需要申请一个内存,就调用malloc()函数,如果不需要使用这个内存了,就调用free()函数。
(3)
<1>那么va_arg()这个宏有什么用呢?很简单,解析可变参数
<2>举个例子:例如printf(“%s %d %s”,“hello”,5,“world”);第一次调用va_arg,那么就是使用的"hello"参数。第二次调用va_arg,就是使用的数字5。第三次调用va_arg,就是使用的"world"参数。

va_start( va_list arg_ptr, prev_param )
va_arg( va_list arg_ptr, type )
va_end( va_list arg_ptr )

这三个宏应该如何使用

(1)知道这三个宏的大体作用之后,那么应该如何使用呢?我这里先拿chatgpt生成的代码进行讲解。
(2)这个sum()函数很简单,就是将一些浮点数据进行求和计算。第一个参数count是告诉函数,可变参数有几个。
(3)
<1>首先解释va_start()这个宏,因为我们开始要使用可变参数了。所以要调用这个宏。
<2>第一个参数默认是一个va_list类型参数。没有什么可说了,规定就是这样的。如果你们硬要问为什么,自己去看底层实现。我这里只是讲解如何使用啊。
<3>第二个参数是,最后一个固定参数。可能有人会问了,最后一个固定参数是什么意思?拿这里的sum()函数为例,这个函数固定会传入count,那么count就是最后一个固定参数。
如果一个函数定义是void UART_printf(uint32_t baseAddress, const char format,…)呢?很简单,因为会固定传入baseAddress和format这两个参数,所以va_start()第二个参数传入*format。
(4)
<1>现在开始解释va_arg()这个宏了。
<2>va_arg()这个宏的第一个参数和va_start()是一样的,都是一个va_list类型数据。
<3>va_arg()的第二个参数,需要根据可变参数数据类型而定。比如,这里的可变参数传入的都是浮点数据,所以第二个参数的double。如果,这里的可变参数传入的是整型数据,第二个参数就是int。大家如果没有明白的话,不用慌,后面我分析肯哥的代码的时候。还会进行讲解。
<4>va_arg()最后将可变参数返回出来。
(5)va_end()这个宏使用起来也很简单,与va_start()和va_arg()的第一个参数是一样的,一个va_list类型数据。

#include <stdio.h>
#include <stdarg.h>

double sum(int count, ...)
{
    double total = 0.0;
    va_list args;
    va_start(args, count);
    
    for (int i = 0; i < count; i++) {
        double num = va_arg(args, double);
        total += num;
    }
    
    va_end(args);
    
    return total;
}

int main()
{
    double result = sum(5, 1.2, 2.3, 3.4, 4.5, 5.6);
    printf("Sum: %f\n", result);
    
    return 0;
}

刨析肯哥的代码

(1)看了上面的解释,肯定还有很多人不明白。没事,不用慌,我将在这里刨析肯哥的代码加深你们对可变参数的理解。
(2)需要注意的是,肯哥的代码,我只会讲解与可变参数有关的部分。
(3)
<1>因为肯哥的这个代码里面,最后一个固定参数是fmt,所以va_start()的第二个参数传入fmt。
<2>我们有没有一个疑惑,为啥肯哥的代码里面,不需要像chatgpt生成的代码那样,传入参数告诉程序,现在有多少个可变参数呢?
很简单,while(*fmt)语句,fmt必定是一个字符串。字符串最后一个字节存储的是什么?0,所以说,可以通过遍历到0,那么字符结束。而这个字符里面,有多少个"%"号,那么就有多少个可变参数。
<3>如果遍历到了%,那么就让Fill_Flag = 1;然后开始处理可变参数。
因为我们规定了,%s是字符串,那么va_arg(ap, char *);这样写。同时使用Str这个char * 型数据接收返回值。
而%d和%x都是整型数据,所以 va_arg(ap, int);这样写。同时使用使用int型数据接收返回值。
<4>最后,while循环检测到了0,表示字符串结束。调用va_end();这个宏进行处理。

int xprintf(char *fmt, ...)
{
    char *Str;
    int  Int_Data;
    uchar Fill_Flag = 0;

    va_list ap;
    va_start(ap, fmt);

    while(*fmt)
    {
        if ((*fmt != '%') && (Fill_Flag == 0))
        {
            Push_To_TX_Buffer(*fmt++);
            continue;
        }

        if (*fmt == '%')
        {
            fmt++;
            Align_Bit = 0;
            Fill_Flag = 1;
        }
        
        switch(*fmt)
        {
            case 's':
                 Str = va_arg(ap, char *);
                 for (; *Str; Str++)
                    Push_To_TX_Buffer(*Str);
                 Fill_Flag = 0;
                 Align_Bit = 0;
                 break;

            case 'd':
                 Int_Data = va_arg(ap, int);
                 IntToStr(Int_Data);
                 for (Str=Simple_Prn_Buf; *Str; Str++) {
                    Push_To_TX_Buffer(*Str);
                 }
                 Fill_Flag = 0;
                 Align_Bit = 0;
                 break;

            case 'x':
                 Int_Data = va_arg(ap, int);
                 HexToStr(Int_Data, 0); //小写
                 for (Str=Simple_Prn_Buf; *Str; Str++) {
                    Push_To_TX_Buffer(*Str);
                 }
                 Fill_Flag = 0;
                 Align_Bit = 0;
                 break;

            case 'X':
                 Int_Data = va_arg(ap, int);
                 HexToStr(Int_Data, 1); //大写
                 for (Str=Simple_Prn_Buf; *Str; Str++) {
                    Push_To_TX_Buffer(*Str);
                 }
                 Fill_Flag = 0;
                 Align_Bit = 0;
                 break;

            default:
                 //Push_To_TX_Buffer(*fmt);
                 Align_Bit = *fmt - '0';
                 if (Align_Bit > 9)
                    Align_Bit = 9;
                 break;
        }
        fmt++;
    }
    va_end(ap);

    return 0;
}
————————————————
版权声明:本文为CSDN博主「架构师李肯」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/szullc/article/details/128245356

如何让所有单片机的所有串口实现printf函数呢

(1)有了上面的基础之后,对于可变参数我们已经有了一定了理解了。那么如何自己编写一个函数,让所有单片机的所有串口实现printf函数呢?
(2)我们都知道,对于单片机,使用printf需要进行重映射,而且还只可以让一个串口进行重映射,不是很方便。那么,再次我将利用上面的知识,和大佬们的博客写一个能够给所有单片机的所有串口进行printf的函数。
(3)注意,这里的vsnprintf()函数,就是将可变参数按照printf中的语法格式,格式化传入到format中,然后最终的数据传入TxBuffer[]这个数组里面。顺便返回格式化了多少字节数据。文章来源地址https://www.toymoban.com/news/detail-504289.html

/* 作用 : 用于多路串口重定义
 * 传入参数 : 
     baseAddress : 要打印的串口地址,UARTx_BASE,x可为0,1,2,3,4,5,6,7
     format : 需要打印的东西
     ... : 如果是打印字符,输入%c。有符号数字,%d。用法与printf一样
 * 返回值 : 无
*/
void UART_printf(uint32_t baseAddress, const char *format,...)
{
    uint32_t length;
    va_list args;
    uint32_t i;
    char TxBuffer[128] = {0};

    va_start(args, format);
    length = vsnprintf((char*)TxBuffer, sizeof(TxBuffer), (char*)format, args);
    va_end(args);

    for(i = 0; i < length; i++)
		{
			//根据不同单片机的串口发送函数,依次发送TxBuffer[i]数据。
			//以下以MSP430为例
			USCI_A_UART_transmitData(baseAddress, TxBuffer[i]);
			//以下以TM4C123为例
			while(UARTBusy(baseAddress));
			UARTCharPut(baseAddress,TxBuffer[i]);
			//以下以STM32F103为例
			USART_SendByte(baseAddress,TxBuffer[i]);
			while(USART_GetFlagStatus(baseAddress,USART_FLAG_TC) == RESET);
		}
}

到了这里,关于如何编写一个可变参数函数?如何让所有单片机的所有串口实现printf函数?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • ###51单片机学习(2)-----如何通过C语言运用延时函数设计LED流水灯

      前言:感谢您的关注哦,我会持续更新编程相关知识,愿您在这里有所收获。如果有任何问题,欢迎沟通交流!期待与您在学习编程的道路上共同进步。       目录 一.  延时函数的生成  1.通过延时计算器得到延时函数  2.可赋值改变的延时函数  二.  LED模块编写原理 

    2024年02月19日
    浏览(42)
  • 【C】【C++】可变参数、不定参函数的使用

    C 语言中的可变参数写法: ... 1.1 可变宏函数 以日志举例,我们写入日志时只需要输入关键信息,行号文件等由宏函数补全 这其中,我们需要输入的信息是格式不定的,需要用到可变参数 输出结果: C语言库中的宏 __FILE__ :字符串,记录当前文件名 __LINE__ :整型,记录当前

    2024年02月09日
    浏览(37)
  • 单片机之独立按键(多种按键代码编写)

    单片机之独立按键原理:按键右端接GND,左边接到单片机的IO口,同时接了一个上拉电阻,当按键未按下时,P2.0为高电平(学过模电的人都应该知道为什么吧)当按键按下时,相当于P2.0口与GND相连为 低电平。

    2024年02月03日
    浏览(58)
  • 【51单片机】如何设置中断函数(场景:在定时器工作完跳转到中断程序时,怎么识别我们的中断程序在哪里呢?)

    前言 大家好吖,欢迎来到 YY 滴单片机系列 ,热烈欢迎! 本章主要内容面向接触过单片机的老铁 本章是【利用定时器和中断实现一个简单项目】中的一部分,感兴趣的老铁可以跳转传送门查看 传送门 欢迎订阅 YY 滴C++专栏!更多干货持续更新!以下是传送门! YY的《C++》专

    2024年02月19日
    浏览(49)
  • 【C++】C++11右值引用|新增默认成员函数|可变参数模版|lambda表达式

    在C++11之前,我们只有引用的概念,没有接触到所谓的左值引用或者是右值引用这种概念,从C++11开始,增加了右值引用的概念,那么现在我们将对引用进行一个概念上的区分。在此之前我们所说的引用都是左值引用,对于左值引用相关的内容,可以去看一看博主之前写的文章

    2024年02月15日
    浏览(57)
  • 51单片机开发环境搭建 - VS Code 从编写到烧录

    我安装并测试成功的环境: 操作系统:Windows 10 (22H2) 单片机:STC89C52RC Python version: 3.7.6 在这之前,给51单片机写程序是用 Keil 5(编写+编译)、STC-ISP(烧录),由于切换应用比较麻烦,并且不够美观,所以尝试使用熟悉的 Visual Studio Code 来完成这些操作。 stcgal 是用来向STC单片

    2023年04月24日
    浏览(57)
  • 【单片机】温控系统参数辨识及单片机PID控制

    半导体制冷片正向通电制冷,反向通电制热。系统采用半导体制冷片(帕尔贴)作为执行单元,帕尔贴下端连接水冷系统进行散热,上端为导热铜片,铜片上贴有热敏电阻用于测量系统的温度。 控制系统采用4个MOS管组成的H桥驱动电路,通过PWM进行加热制冷温度控制。 一般的

    2024年02月03日
    浏览(40)
  • C++11可变参数模板(typename... Args模板参数包或class... Args)(Args... args函数参数包)(递归展开与参数包展开(只支持C++17))

    C++可变参数是指函数的参数个数是可变的,可以在函数定义时不确定参数的个数,需要在函数体内通过特定的语法来处理这些参数。C++11标准引入了新的可变参数模板,使得可变参数的处理更加方便和灵活。在函数定义时,可以使用省略号(…)来表示可变参数,然后通过va_li

    2024年02月08日
    浏览(41)
  • 国民技术 MCU N32单片机所有系列---JLink添加芯片,方便使用Keil和JFlash

    1.将下图JLink tool adds Nations chip V1.0.12文件中选中的文件复制到自己电脑的JLINK的目录下(JLINK默认的下载路径是C:Program FilesSEGGERJLink   或者  C:Program Files(x86)SEGGERJLink) 2.然后直接把JLink tool adds Nations chip V1.0.12文件中的JLinkDevices.xml复制替换到自己电脑的JLINK的目录下的JLin

    2024年02月03日
    浏览(48)
  • 【概念理解】单片机控制舵机之PWM波参数的设置

    我们要知道,SG90舵机接收的PWM信号的参数:f=50Hz,T=1/f,所以周期为20ms。 当高电平的脉宽在0.5ms-2.5ms之间时舵机就可以对应旋转到不同的角度。 换句话说,我们要用单片机产生一个 周期(20ms) 的PWM波,然后获得对应这些时长(分别是 0.5ms 、 1ms 、 1.5ms 、 2ms 、 2.5ms )的 高电

    2024年02月10日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包