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