蓝桥杯国赛——第五战(C语言易用库的移植)
1.
C 标准库 – <stdarg.h> | 菜鸟教程 (runoob.com)
没有学过C++也不要劝退哈(C标准库也有),因为我也没学过,只是之前看到有人使用这个库里面的函数来传送不定长的数据,受到启发,想要来看看这个库里面有什么神奇的东西。一起来学一下吧。
此头文件定义宏来访问未命名参数列表中的各个参数,这些参数的数量和类型对被调用函数来说是未知的。通过在常规命名形参后面加上逗号和三个点(,…),函数可以接受数量不等的附加实参,而不需要声明相应的形参: Return_type function_name(…);
要访问这些额外的参数,可以使用宏va_start, va_arg和va_end,在这个头文件中声明:
- 首先,va_start将变量参数列表初始化为va_list。
- va_arg的后续执行将以传递给函数的相同顺序生成附加参数的值。
- 最后,在函数返回之前执行va_end。
/* va_arg example */ #include <stdio.h> /* printf */ #include <stdarg.h> /* va_list, va_start, va_arg, va_end */ /***下面是指导手册中的例程源码***/ int FindMax (int n, ...) { int i,val,largest; va_list vl; va_start(vl,n); largest=va_arg(vl,int); for (i=1;i<n;i++) { val=va_arg(vl,int); largest=(largest>val)?largest:val; } va_end(vl); return largest; }
不关注这个函数的功能,先看看这些宏定义怎么搭配,以及如何实现不定长变量的传输。
- va_list:声明了一个该变量类型的值——vl;
- va_start:将变量参数列表传递给vl,按照原有顺序;
- va_arg:获取参数列表的值,通过“第二个参数”决定读取什么类型的值,此处是int型;
- tip:va_arg的返回值就已经是数值了,而vl,我们可以认为它是一种指针的属性,存着链表的地址;
typedef char * va_list;
/*定义一个char型指针变量*/
#define va_start _crt_va_start
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
/*根据传进来的头一个参数,建立和参数列表对应的地址映射*/
#define va_arg _crt_va_arg
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
/*根据user传递的参数类型,如int,确定地址的增量模式,并解地址得到数值*/
至于这个例程实现的功能,看到main就能知道是findmax的功能,求最大值:
int main () { int m; m= FindMax (7,702,422,631,834,892,104,772); printf ("The largest value is: %d\n",m); return 0; }
然而,这些函数怎么读取不定长数据的关键点?仅仅是用第一个参数来表征整个参数列表的个数,的确没有达到我的预期效果;
使用vsprintf可以完成预期效果,将不定类型,不定长度的参数写入一个字符串;
C 库函数 – vsprintf() | 菜鸟教程 (runoob.com)
int vsprintf(char *str, const char *format, va_list arg)
#include <stdio.h> #include <stdarg.h> char buffer[80]; int vspfunc(char *format, ...) { va_list aptr; int ret; va_start(aptr, format); ret = vsprintf(buffer, format, aptr);
/*如果成功,则返回写入的字符总数,否则返回一个负数*/ va_end(aptr); return(ret); } int main() { int i = 5; float f = 27.0; char str[50] = "runoob.com"; vspfunc("%d %f %s", i, f, str); printf("%s\n", buffer); return(0); }
此外,也可以使用vsnprinf,似乎没什么区别;
int vsnprintf (char * s, size_t n, const char * format, va_list arg );
将格式化的数据从变量参数列表写入大小已设置的缓冲区
Keil测试:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { vspfunc("Time_YMD:%02d-%02d-%02d %s",22,15,57,"Hello"); HAL_UART_Transmit(&huart1,(unsigned char *)buffer, strlen(buffer), 50); HAL_UART_Receive_IT(&huart1, (uint8_t *)(&rx_buffer), 1); } int vspfunc(char * format,...) { va_list list; int i; va_start(list,format); i = vsprintf(buffer,format,list); va_end(list); return(i); }
测试效果:每次出发串口中断,就通过vsprintf发送一段指定格式的数据。
思考:
还是没能实现我心里的发送不定长数据的功能。vsprintf把一串不定参数的值传给buff,这与sprintf比起来只多了一个不定参数长度的优点。
但是这种优点仍然需要建立在“所传不定长参数的读取格式可以被有限归纳”的基础上,并且在与串口的交互中,因为串口不是std输出,还是得借助transmit,作用不大。
事实上,即使在C语言中,vsprintf也应该与键盘输入能直接练习,才能最大限度地发挥作用,但是显然这个功能已经有gets()等函数来实现了。
备注:之前是其他文章看到这样使用vsprintf的,来回忆下当时作者用它实现了什么功能?
void Uart_Proc(void) { if (usart1_rx_flag) { count++; Debug_printf("接收数据次数: %d\r\n接收数据长度: %d\r\n接收数据内容:%s",count,usart1_rx_len,receive_data); usart1_rx_flag = 0; usart1_rx_len = 0; } times++; if(times%200==0) { Debug_printf("**********************串口DMA测试************************\r\n"); } HAL_Delay(10); } void Debug_printf(const char *format, ...) { uint32_t length = 0; va_list args; __va_start(args, format); length = vsnprintf((char*)usart1_tx_buffer, sizeof(usart1_tx_buffer), (char*)format, args); UART1_TX_DMA_Send(usart1_tx_buffer, length); }
2.字符串处理函数
char * ptr = "Welcome to my blog!"; char str[] = "Welcome to my blog!";
初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针只把字符串的地址拷贝给指针
for(int i=0;i<6;i++) { putchar(*(str+i)); }
/****************/
/**下面是error写法*/
putchar(*(str++))
指针形式和数组形式定义都可以有加法操作;但其中数组不能用递增;
const char * ptr = "I am handsome, right?";
推荐用const限定符避免错误改写,引起不必要的bug
/************以上少述,感兴趣的同学,阅读各类C语言教程即可************/
strlen():返回字符串的长度,不包括结束字符(即 null 字符、或者说 ' \0 ' 字符);
关键字 sizeof 是一个单目运算符,它的参数可以是数组、指针、类型、对象、函数等(包括结束字符 null、’\0‘);
sizeof 在编译时计算缓冲区的长度。也正是由于在编译时计算,因此 sizeof 不能用来返回动态分配的内存空间的大小。
size_t strlen(const char *str)
strcat():把 src 所指向的字符串追加到 dest 所指向的字符串的结尾
没有检查第一个字符长度是否足够容纳扩充后的字符串;
char *strcat(char *dest, const char *src)
C 库函数 char *strncat(char *dest, const char *src, size_t n) 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止。
char dest[size] = {}; char *strncat(char *dest, const char *src, size_t n) /******使用方法*****/ int i; i = size - strlen(dest) - 1; strncat(dest , src , i);
C 库函数 int strcmp(const char *str1, const char *str2) 把 str1 所指向的字符串和 str2 所指向的字符串进行比较
- 如果返回值小于 0,则表示 str1 小于 str2。
- 如果返回值大于 0,则表示 str1 大于 str2。
- 如果返回值等于 0,则表示 str1 等于 str2。【大小比较按照ASCII码来比较】
零值和非零值的区别;比较的是字符串而不是字符、数组,因此可以用于不同长度的数组中字符串的比较。
尽量不要用来比较字符'A',而用来比较字符串“A”;
int strcmp(const char *str1, const char *str2)
C 库函数 int strncmp(const char *str1, const char *str2, size_t n) 把 str1 和 str2 进行比较,最多比较前 n 个字节。
【模糊搜索的感觉】
int strncmp(const char *str1, const char *str2, size_t n)
C 库函数 char *strcpy(char *dest, const char *src) 把 src 所指向的字符串复制到 dest。
需要注意的是如果目标数组 dest 不够大,而源字符串的长度又太长,可能会造成缓冲溢出的情况。char *strcpy(char *dest, const char *src)
第一,strcpy()的返回类型是char *,该函数返回的是第1个参数的值,即一个字符的地址。第二,第1个参数不必指向数组的开始。这个属性可用于拷贝数组的一部分。
/*****由下面的简单代码说明strcpy的必要性****/ /****下面是error*****/ int main() { char target[20]; target = "Hello"; return 0; } /*因为target本质上是一个地址,并不是变量名,不属于可修改的左值,所以strcpy直接接收指针变量,达成目的*/
C 库函数 char *strncpy(char *dest, const char *src, size_t n) 把 src 所指向的字符串复制到 dest,最多复制 n 个字符。当 src 的长度小于 n 时,dest 的剩余部分将用空字节填充。
没有自动加上终止符的功能,所以size_t n需要比目标数组小一,然后再手动加上\0的终止符;
char *strncpy(char *dest, const char *src, size_t n)
ANSI C库有20多个用于处理字符串的函数,下面总结了一些常用的函数。
● char *strcpy(char * restrict s1, const char * restrict s2);该函数把s2指向的字符串(包括空字符)拷贝至s1指向的位置,返回值是s1。
● char *strncpy(char * restrict s1, const char * restrict s2, size_t n);该函数把s2指向的字符串拷贝至s1指向的位置,拷贝的字符数不超过n,其返回值是s1。该函数不会拷贝空字符后面的字符,如果源字符串的字符少于n个,目标字符串就以拷贝的空字符结尾;如果源字符串有n个或超过n个字符,就不拷贝空字符。
● char *strcat(char * restrict s1, const char * restrict s2);该函数把s2指向的字符串拷贝至s1指向的字符串末尾。s2字符串的第1个字符将覆盖s1字符串末尾的空字符。该函数返回s1。
● char *strncat(char * restrict s1, const char * restrict s2, size_t n);该函数把s2字符串中的n个字符拷贝至s1字符串末尾。s2字符串的第1个字符将覆盖s1字符串末尾的空字符。不会拷贝s2字符串中空字符和其后的字符,并在拷贝字符的末尾添加一个空字符。该函数返回s1。
● int strcmp(const char * s1, const char * s2);如果s1字符串在机器排序序列中位于s2字符串的后面,该函数返回一个正数;如果两个字符串相等,则返回0;如果s1字符串在机器排序序列中位于s2字符串的前面,则返回一个负数。
● int strncmp(const char * s1, const char * s2, size_t n);该函数的作用和strcmp()类似,不同的是,该函数在比较n个字符后或遇到第1个空字符时停止比较。
● char *strchr(const char * s, int c);如果s字符串中包含c字符,该函数返回指向s字符串首次出现的c字符的指针(末尾的空字符也是字符串的一部分,所以在查找范围内);如果在字符串s中未找到c字符,该函数则返回空指针。
返回的指针指向当前寻找的字符所在的位置:
int main () { const char str[] = "http://www.runoob.com"; const char ch = '.'; char *ret; ret = strchr(str, ch); printf("|%c| 之后的字符串是 - |%s|\n", ch, ret); return(0); } |.| 之后的字符串是 - |.runoob.com|
● char *strpbrk(const char * s1, const char * s2);如果s1字符中包含s2字符串中的任意字符,该函数返回指向s1字符串首位置的指针;如果在s1字符串中未找到任何s2字符串中的字符,则返回空字符。
感觉用处就用于定位指定字符所在位置就好;【尽管第二个参数为数组指针,感觉用处也不大,就按照单个字符查找挺好】
int main () { const char str1[] = "abcde2fghi3jk4l"; const char str2[] = "34"; char *ret; ret = strpbrk(str1, str2); if(ret) { printf("第一个匹配的字符是: %c\n", *ret); } else { printf("未找到字符"); } return(0); } 第一个匹配的字符是: 3
● char *strrchr(const char * s, char c);该函数返回s字符串中c字符的最后一次出现的位置(末尾的空字符也是字符串的一部分,所以在查找范围内)。如果未找到c字符,则返回空指针。
int main () { int len; const char str[] = "https://www.runoob.com"; const char ch = '.'; char *ret; ret = strrchr(str, ch); printf("|%c| 之后的字符串是 - |%s|\n", ch, ret); return(0); } |.| 之后的字符串是 - |.com|
● char *strstr(const char * s1, const char * s2);该函数返回指向s1字符串中s2字符串出现的首位置。如果在s1中没有找到s2,则返回空指针。
类似于全局搜索,不支持模糊搜索;
int main() { const char haystack[20] = "RUNOOB"; const char needle[10] = "NOOB"; char *ret; ret = strstr(haystack, needle); printf("子字符串是: %s\n", ret); return(0); } 子字符串是: NOOB
● size_t strlen(const char * s);该函数返回s字符串中的字符数,不包括末尾的空字符。
请注意,那些使用const关键字的函数原型表明,函数不会更改字符串
/*****参考书上的一个排序算法****/
3.memset函数:
C 库函数 void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)