蓝桥杯国赛——第五战(C语言易用库的移植)

1. <cstdarg>    or  <stdarg.h>

<cstdarg> (stdarg.h) - C++ Reference (cplusplus.com)

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 个字符。

 

posted @   charonplus  阅读(127)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示