1‘#’ 和 ‘##’ 属于预处理标记。‘#’ 和 ‘##’ 用于类似函数的宏定义中(或者简称为宏定义函数)。2‘__VA_ARGS__’ 是 C99 引入的用于支持宏定义函数中使用可变参数。

操作符 ‘#’

在宏定义展开的时候,标记 ‘#’ 用于将 ‘#’ 后面的宏定义函数中的参数转化为对应的字符串。宏定义函数的参数与预处理标记 ‘#’ 之间出现的每一个空格都会被删除,并删除第一个预处理标记之前和最后一个预处理标记之后的空白字符,但是宏定义函数参数中的空格会保留。

其中,空参数转化为为空,即宏定义函数入参为空,那么展开的时候也为空。

上面的这段话比较难理解,这里为了准确地传达其意义,我们来看一个示例程序。

在看到代码后,可以先猜猜可能的输出结果,如果你答对了,那就是真的会了!
注意,这里我基于 RT-Thread QEMU BSP 进行代码展示,代码真实编译通过,运行正常。

示例程序 A

请看以下代码:

 1#include  2#include  3 4#define mkstr(var) (#var) 5 6int main(void) 7{ 8    rt_kprintf("hello rt-thread"); 910    rt_kprintf(mkstr(hello rt-thread));1112    return 0;13}

请问:

  • 它能编译通过吗?
  • 它能输出什么内容?

答案:

  • 它可以正常编译通过
  • 它输出的内容1hello rt-thread
    2hello rt-threadmsh />

从上面输出的信息可以看到,hello rt-thread 字符串被准确地输出到了控制台,但是没有增加回车换行。其中 msh /> 字符串是 RT-Thread 控制台回显。

如上,代码 rt_kprintf(mkstr(hello rt-thread)); 中的 hello rt-thread 在没有加引号的情况下,被转化成了字符串。

示例程序 B

为示例程序 A 打印的字符串增加回车换行。

 1#include  2#include  3 4#define mkstr(var) (#var) 5 6int main(void) 7{ 8    rt_kprintf("hello rt-thread"); 910    rt_kprintf(mkstr(hello rt-thread));1112    return 0;13}

有了示例 A 的基础,示例 B 那就是 soeasy,直接在原有的基础上增加 转义字符即可输出回车换行。

输出结果如下:

1hello rt-thread2hello rt-thread3msh />

示例程序 C

我们在示例程序 B 中成功增加了回车换行的输出,但是你有没有想过一个问题,如果你又很多地方用到 mkstr 宏定义函数输出信息,那你是不是每一个地方都要增加 ,这岂不是很累,有没有好的方法?

好方法当然有,下面介绍下程序中常用的方式,利用 C 语言相邻字符串自动拼接的特性(当然,这是编译器支持的)。代码如下:

 1#include  2#include  3 4#define mkstr(var) (#var"") 5 6int main(void) 7{ 8    rt_kprintf("hello rt-thread"); 910    rt_kprintf(mkstr(hello rt-thread));1112    return 0;13}

以上代码在 #var 后面增加了一个字符串 ,我们来看宏定义展开过程:

1-> rt_kprintf(mkstr(hello rt-thread));2-> rt_kprintf("hello rt-thread""");3-> rt_kprintf("hello rt-thread");

示例程序 D

预处理标记 # 的基本用法已经展示完了,但怎么理解 “宏定义函数的参数与预处理标记 ‘#’ 之间出现的每一个空格都会被删除,并删除第一个预处理标记之前和最后一个预处理标记之后的空白字符”?

请看下面的代码:

 1#include  2#include  3 4#define mkstr(var) ("aa"  #  var  "bb") 5 6int main(void) 7{ 8    rt_kprintf("hello rt-thread"); 910    rt_kprintf(mkstr(hello rt-thread));1112    return 0;13}

宏定义 mkstr(var) ("aa" # var "bb") 中的 # var 中间有两个空格,根据定义,# 号与宏定义函数参数 var 中间的两个空格会被删除,但是 var 参数中的 hello rt-thread 中的空格不会被删除。

继续,根据定义,宏定义 mkstr(var) ("aa" # var "bb") 中只有一个预处理标记 #,其作为第一个和最后一个预处理标记,它前面和后面的空格都会被删除。

因此,以上代码预计输出结果为:

1hello rt-thread2aahello rt-threadbb3msh />

示例程序 E

在实际操作时,操作符 # 被常用于枚举转字符串。以下代码截取自我的 FlexibleButton 按键库 的示例程序。

 1#define ENUM_TO_STR(e) (#e) 2 3typedef enum 4{ 5    USER_BUTTON_0 = 0, 6    USER_BUTTON_1, 7    USER_BUTTON_2, 8    USER_BUTTON_3, 9    USER_BUTTON_MAX10} user_button_t;1112static char *enum_btn_id_string[] = {13    ENUM_TO_STR(USER_BUTTON_0),14    ENUM_TO_STR(USER_BUTTON_1),15    ENUM_TO_STR(USER_BUTTON_2),16    ENUM_TO_STR(USER_BUTTON_3),17    ENUM_TO_STR(USER_BUTTON_MAX),18};

操作符 ‘##’

‘##’ 是预处理拼接标记。在宏定义展开的时候,将 ‘##’ 左边的内容,与 ‘##’ 右边的内容拼接到一起。

注意,对于任何一种形式的宏定义,‘##’ 预处理标记都不应出现在替换列表的开头或结尾。

关于替换列表:

例如宏定义 ‘#define aa(x, y) (x##y)’ 后面的部分 ‘x##y’ 就是替换列表。

预处理拼接符 ## 常用于使用宏定义批量生成函数或者变量。

示例程序 F

 1#include  2#include  3 4#define my_math(x, y) (x##e##y) 5 6int main(void) 7{ 8    rt_kprintf("hello rt-thread"); 910    printf("%e", my_math(3, 4));1112    return 0;13}

以上代码是科学计数法的格式输出到控制台,输出内容如下:

1hello rt-thread23.000000e+043msh />

示例程序 G

用预处理拼接符 ## 批量生成函数或者变量。

以下代码截取自 RT-Thread finsh_api.h,该段代码用于导出 Finsh 命令,代码如下所示:

1#define FINSH_FUNCTION_EXPORT_CMD(name, cmd, desc)                      2    const char __fsym_##cmd##_name[] SECTION(".rodata.name") = #cmd;    3    const char __fsym_##cmd##_desc[] SECTION(".rodata.name") = #desc;   4    RT_USED const struct finsh_syscall __fsym_##cmd SECTION("FSymTab")= 5    {                           6        __fsym_##cmd##_name,    7        __fsym_##cmd##_desc,    8        (syscall_func)&name     9    };

该宏定义的应用,如 list_timer 命令,如下所示:

1FINSH_FUNCTION_EXPORT_CMD(list_timer, list_timer, list timer in system);

标识符 `VA_ARGS`

__VA_ARGS__ 是在 C99 中增加的新特性。虽然 C89 引入了一种标准机制,允许定义具有可变数量参数的函数,但是 C89 中不允许这种定义可变数量参数的方式出现在宏定义中。C99 中加入了 __VA_ARGS__ 关键字,用于支持在宏定义中定义可变数量参数,用于接收 ... 传递的多个参数。1

__VA_ARGS__ 只能出现在使用了省略号的像函数一样的宏定义里。例如 #define myprintf(...) fprintf(stderr, __VA_ARGS__)。

解析不定参

通过宏定义,将多个参数传递给函数,那么函数是如何解析不定参的呢?

这就需要使用标准库头文件 中的三个宏,分别是 “va_start()”、“va_arg()”、“va_end()”,以及一个可变参类型 “va_list”,示例使用方式借用 RT-Thread 中的 rt_sprintf 的实现,代码如下所示:

 1rt_int32_t rt_sprintf(char *buf, const char *format, ...) 2{ 3    rt_int32_t n; 4    va_list arg_ptr; 5 6    va_start(arg_ptr, format); 7    n = rt_vsprintf(buf, format, arg_ptr); 8    va_end(arg_ptr); 910    return n;11}
  • 首先使用 “va_list” 类型定义一个变量 “arg_ptr”
  • 然后调用 “va_start(arg_ptr, format);” 函数,第一个入参是 “va_list” 类型,第二个参数是 “rt_sprintf” 函数参数列表中的最后一个定参 “format”
  • 然后,通过调用 “rt_vsprintf” 函数,根据 “format” 来解析不定参,并将结果存放到 “buf” 中
  • 最后,使用 “va_end(arg_ptr);” 来释放不定参列表占用的资源

带 ‘#’ 的标识符 `#VA_ARGS`

预处理标记 ‘#’ 用于将宏定义参数转化为字符串,因此 #__VA_ARGS__ 会被展开为参数列表对应的字符串。

示例:

1#define showlist(...) put(#__VA_ARGS__)23测试如下:4showlist(The first, second, and third items.);5showlist(arg1, arg2, arg3);67输出结果分别为:8The first, second, and third items.9arg1, arg2, arg3

带 ‘##’ 的标识符 `##VA_ARGS`

##__VA_ARGS__ 是 GNU 特性,不是 C99 标准的一部分,C 标准不建议这样使用,但目前已经被大部分编译器支持。

标识符 ##__VA_ARGS__ 的意义来自 ‘##’,主要为了解决一下应用场景:

1#define myprintf_a(fmt, ...) printf(fmt, __VA_ARGS__)2#define myprintf_b(fmt, ...) printf(fmt, ##__VA_ARGS__)34应用:5myprintf_a("hello");6myprintf_b("hello");78myprintf_a("hello: %s", "world");9myprintf_b("hello: %s", "world");

这个时候,编译器会报错,如下所示:

1applicationsmain.c: In function 'main':2applicationsmain.c:26:57: error: expected expression before ')' token3 #define myprintf_a(fmt, ...) printf(fmt, __VA_ARGS__)4                                                         ^5applicationsmain.c:36:5: note: in expansion of macro 'myprintf_a'6     myprintf_a("hello");

为什么呢?

我们展开 myprintf_a("hello"); 之后为 printf("hello",)。因为没有不定参,所以,__VA_ARGS__ 展开为空白字符,这个时候,printf 函数中就多了一个 ‘,’(逗号),导致编译报错。而 ##__VA_ARGS__在展开的时候,因为 ‘##’ 找不到连接对象,会将 ‘##’ 之前的空白字符和 ‘,’(逗号)删除,这个时候 printf 函数就没有了多余的 ‘,’(逗号)。

另外还有一些关于c++ Linux后台服务器开发的一些知识点分享:Linux,Nginx,MySQL,Redis,P2P,K8S,Docker,TCP/IP,协程,DPDK,webrtc,音视频等等视频。

喜欢的朋友可以后台私信【1】获取学习视频

附上一份c++ Linux后台服务器开发 学习课程大纲给大家

c语言字符串截取_一文搞懂 C 语言 #、##、__VA_ARGS__相关推荐

  1. r 语言计算欧氏距离_一文搞懂常用R语言统计值计算:打倒描述性统计拦路虎

    本文来自:R语言:用R计算各种统计值 作者:生物信息学习 目录: 求极差(range) 做频数分布表和频数分布图(graph of frequency distribution) 算术平均数(mean ...

  2. python语言语句快的标记是什么_一文搞懂Python程序语句

    原标题:一文搞懂Python程序语句 程序流 Python 程序中常用的基本数据类型,包括: 内置的数值数据类型 Tuple 容器类型 String 容器类型 List 容器类型 自然的顺序是从页面或 ...

  3. pythonxpath定位_一文搞懂 XPath 定位

    一文搞懂XPath 定位 XPath (XML Path Language) 是一门在 XML 文档中查找信息的语言,可用来在 XML 文档中对元素和属性进行遍历. XPath定位在爬虫和自动化测试中 ...

  4. 一文搞懂C语言回调函数

    转载自:https://segmentfault.com/a/1190000008293902?utm_source=tag-newest 博主:Rdou Typing 来源:segmentfault ...

  5. c语言位向量机伞_一文读懂C语言精华-指针变量和指向指针的指针

    1978年贝尔实验室正式发表C语言,受到众多IT从业者的热捧,即使41年过去了,C语言仍然牢牢占据最受欢迎编程语言前二的位置,许多人的程序开发之路也是从C语言开始的. 编程语言受欢迎排名 C语言简洁紧 ...

  6. c语言字符串截取_笔记 | 自学Python 05:数据类型之字符串

    字符串,就是一个个字符组成的有序的序列,是字符的集合,在python中通常使用单引号.双引号和三引号引住的字符序列,由数字.字母.下划线组成. 从以下6个方面来了解: ①字符串是不可变的 上一期我们介 ...

  7. 语言 标签倾斜 绘图_一文搞懂ggplot2:老板再也不用担心我的科研绘图

    本文主引用:R可视化19|ggplot2绘制常用30+个靓图(附R code) 作者:pythonic生物人 参考文献:R语言 ggplot2 绘图入门,看完你就理解ggplot2的绘图逻辑了 202 ...

  8. python数组类型_一文搞懂Python中的所有数组数据类型

    关于我 编程界的一名小小程序猿,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. 联系:hylinux1024@gmail ...

  9. char类型怎么输入 c语言_还没搞懂C语言指针?这里有最详细的纯干货讲解(附代码)...

    21ic综合自网络信息 指针对于C来说太重要.然而,想要全面理解指针,除了要对C语言有熟练的掌握外,还要有计算机硬件以及操作系统等方方面面的基本知识.所以本文尽可能的通过一篇文章完全讲解指针. 为什么 ...

最新文章

  1. Java网络爬虫实操(5)
  2. C语言图形界面的编程
  3. 公安部全面查找改革开放以来失踪被拐儿童 失踪被拐儿童父母和疑似被拐人员快去采集DNA
  4. 【人脸识别】初识人脸识别
  5. vim匹配特定的行并删除
  6. 无障碍开发(三)之ARIA aria-***属性值
  7. 令人难忘的初吻描写......
  8. php 查询 判断 语句,关于php的判断语句
  9. Spring 定时器结合线程池
  10. LeetCode(965)——单值二叉树(JavaScript)
  11. Python+OpenCV:特征匹配(Feature Matching)
  12. java中什么是匿名接口_Java中接口(interface)和匿名类
  13. Product of Array Except Self
  14. [导入]XACT与X3DAudio整合的问题
  15. cnn风格迁移_CNN图像风格迁移的原理及TensorFlow实现
  16. Spring学习传送门
  17. 转:明茨伯格:组织不需要英雄,只需要有雅量的领导
  18. 日志技术之JUL入门
  19. Qt源码分析--QPaintEngine
  20. 2021上海酵素展暨中国酵素节资讯食用酵素的营养功效及研究进展

热门文章

  1. UI设计摘要背景素材|简单分层PSD格式化模板,为项目增加背景
  2. oracle给日期加特定天数,oracle按照日期求连续天数的数据sql
  3. systemd-run 中文手册
  4. 自旋锁:pthread_spinlock_t,互斥锁:pthread_mutex_t,条件变量:pthread_cond_t,读写锁:pthread_rwlock_t
  5. TCP状态转移:有限状态机
  6. /proc 文件系统并使用/proc 进行输入
  7. sklearn搭建线性模型的总结
  8. python数据分析的应用场景_第一章Python数据分析概述
  9. python写的软件怎么逆向_python逆向工程:通过代码生成类图
  10. 单片机与普通微型计算机不同在于,单片机与普通计算机的不同之处在于什么,单片机与普通微型计算机的不同...