同事C代码中的#、##把我秀了

开发 后端
#和##对于大部分C语言玩得还算比较溜的朋友并不是很陌生,不过能把这两个知识点游刃有余的应用到所在代码中的每个角落,似乎并没有几个人能够做到,学的时候朗朗上口,而编码的时候却抛之脑后。

 [[438431]]

正文

大家好,我是bug菌!

#和##对于大部分C语言玩得还算比较溜的朋友并不是很陌生,不过能把这两个知识点游刃有余的应用到所在代码中的每个角落,似乎并没有几个人能够做到,学的时候朗朗上口,而编码的时候却抛之脑后。

但是今天bug菌还是想重新介绍这两个“兄弟”,希望大家能够写出"秀"一点的代码~

1.#和##基础

对于这两个语法的功能都比较简单,且都是在预处理阶段做一些工作 :

  •  #主要是将宏参数转化为字符串
  •  ##主要是将两个标识符拼接成一个标识符

没点代码似乎并不是那么形象 : 

参考demo: 

  1. 1#include <stdio.h>  
  2.  2#include <stdlib.h>  
  3.  3  
  4.  4//#的简单使用   
  5.  5#define STR(str) #str  
  6.  6  
  7.  7//##的简单使用  
  8.  8#define  CMB(a,b) a##b  
  9.  9  
  10. 10int main(int argc, char *argv[]) {  
  11. 11  
  12. 12    int CMB(uart,1) = 5;  
  13. 13    int CMB(uart,2) = 10;  
  14. 14  
  15. 15    printf("#的简单使用:\r\n");  
  16. 16    printf("%s\r\n",STR(3.1415));  
  17. 17    printf("%s\r\n",STR(abcd));  
  18. 18  
  19. 19    printf("##的简单使用:\r\n");  
  20. 20    printf("%d\r\n",uart1);      
  21. 21    printf("%d\r\n",uart2);  
  22. 22  
  23. 23    return 0;  
  24. 24} 

输出结果:

从结果上看来似乎#仅仅只是代替了字符串的双引号,而##却实现了标识符的拼接,这样就为编码标识符的处理上能够带来更多的可玩性。

那么,下面bug菌跟大家具体展示一下他们的常用技巧:

2.#的玩法

(1)标识符的“字符串变量"

“#”一般结合打印语句组合成一个宏定义,可以方便的打印相关信息,下面给个简单的实例就明白了。 

  1. 1#include <stdio.h>  
  2.  2#include <stdlib.h>  
  3.  3  
  4.  4//#打印调试   
  5.  5#define DebugLogExpr(Expr)     printf("%s : %d\r\n",#Expr, Expr);    
  6.  6  
  7.  7//私有参数访问   
  8.  8int sFucntion(void)  
  9.  9{  
  10. 10    static int var = 10 
  11. 11    return var;  
  12. 12}   
  13. 13  
  14. 14int main(int argc, char *argv[]) {  
  15. 15  
  16. 16    int DebugVar = 50 
  17. 17  
  18. 18    DebugLogExpr(DebugVar);     //直接打印变量名和变量   
  19. 19    DebugLogExpr(100/5);        //打印表达式及结果   
  20. 20    DebugLogExpr(sFucntion());  //打印相关函数名及结果   
  21. 21  
  22. 22    return 1;  
  23. 23} 

输出结果:

这样的话就不需要总是采用双引号来单独书写,同时你还可以继续扩展构造更加灵活的宏。

(2)结合##进行字符串拼接打印

前面介绍了##进行标识符的拼接,那么实现拼接标识符转化为字符串看来很简单吧,于是你会编写了如下代码: 

  1.  1#include <stdio.h>  
  2.  2#include <stdlib.h>  
  3.  3  
  4.  4//#的简单使用   
  5.  5#define STR(str) #str  
  6.  6  
  7.  7//##的简单使用  
  8.  8#define  CMB(a,b) a##b  
  9.  9  
  10. 10int main(int argc, char *argv[]) {  
  11. 11  
  12. 12    int CMB(uart,1) = 5;  
  13. 13  
  14. 14    printf("%s\r\n",STR(CMB(uart,1)));  
  15. 15  
  16. 16    return 0;  
  17. 17} 

暗自欢喜的编译着,然而却得到了如下结果:

得到的并不是拼接以后你想要的uart1,难道不能这么玩?当然不是,不然也不会在这里拿出来说 。

首先要知道原因 : 进行宏定义嵌套的情况,#或者##仅在当前宏有效,嵌套宏中不会再次展开,既然当前宏无法展开,那么我只能再加一级宏定义作为转换宏进行展开,看能不能解决该问题: 

  1.  1 #include <stdio.h>  
  2.  2#include <stdlib.h>  
  3.  3  
  4.  4//#的简单使用   
  5.  5#define STR(str) #str  
  6.  6  
  7.  7//##的简单使用  
  8.  8#define  CMB(a,b) a##b  
  9.  9  
  10. 10#define STR_CON(str) STR(str)  //转换宏   
  11. 11  
  12. 12int main(int argc, char *argv[]) {  
  13. 13  
  14. 14    int CMB(uart,1) = 5;  
  15. 15  
  16. 16    printf("%s\r\n",STR_CON(CMB(uart,1)));  
  17. 17  
  18. 18    return 0;  
  19. 19} 

此时输出的结果符合我们的预期:

首先进行第一层转换宏替换处理掉##拼接符得到str(uart1),然后进行字符串转换符的处理为uart1字符串打印输出,当然以后你会遇到一些复杂的,不过要诀就是宏替换只会处理当前的#或者##,否则就需要增加转换宏提前进行宏替换展开。

所以采用##拼接出来的标识符想要打印输出的话,使用#进行转换是最直接、方便的。

3.##的玩法

##拼接符的玩法有点多,甚至有些还比较绕,当然如果你游刃有余的话,这对于重构代码是一把“ 利器 ”。

(1)在结构体定义中的妙用

下面是bug菌经常在项目代码中用到的##结构体定义法,也是非常多开源代码中惯用的做法,相比常规的结构体定义法,确实省去很多重复的代码。

比如下面的参考代码 :  

  1. 1#include <stdio.h>  
  2.  2#include <stdlib.h>  
  3.  3  
  4.  4#define DF_STRUCT(name) typedef struct tag##name name;\  
  5.  5                         struct tag##name  
  6.  6  
  7.  7DF_STRUCT(DevManage) 
  8.  8{  
  9.  9    int index;   //索引   
  10. 10    int Access;  //权限  
  11. 11                  //...    
  12. 12};  
  13. 13  
  14. 14int main(int argc, char *argv[]) {  
  15. 15  
  16. 16   DevManage stDevManage;  
  17. 17  
  18. 18   stDevManage.index  = 1 
  19. 19   stDevManage.Access = 666 
  20. 20  
  21. 21    printf("Dev Index :%d\n",stDevManage.index );  
  22. 22    printf("Dev Access:%d\n",stDevManage.Access );  
  23. 23  
  24. 24    return 1;  
  25. 25} 

(2)统一宏替换

拼接标识符意味着符号的粒度更高,而这碎片化的符号进行有效的管理,就可以使得符号更加具有通用性和灵活性。

其实这种思想跟我们代码模块话是同样的道理。

来首先我们用一个两层拼接体验一下: 

  1.  1#include <stdio.h>  
  2.  2#include <stdlib.h>  
  3.  3  
  4.  4//假如这是stm32库中的宏   
  5.  5#define GPIO_Pin_0                 ((int)0x0001)  /*!< Pin 0 selected */  
  6.  6#define GPIO_Pin_1                 ((int)0x0002)  /*!< Pin 1 selected */  
  7.  7#define GPIO_Pin_2                 ((int)0x0004)  /*!< Pin 2 selected */  
  8.  8#define GPIO_Pin_3                 ((int)0x0008)  /*!< Pin 3 selected */  
  9.  9  
  10. 10#define USART1              ((int *) 0x1000)  
  11. 11#define USART2              ((int *) 0x2000)  
  12. 12 
  13. 13  
  14. 14//拼接变量   
  15. 15#define UARTX 1  
  16. 16  
  17. 17//最终的组合标识符   
  18. 18#define UART1_CORE  USART1  
  19. 19#define UART1_RX    GPIO_Pin_0  
  20. 20#define UART1_TX    GPIO_Pin_1  
  21. 21 
  22. 22#define UART2_CORE  USART2  
  23. 23#define UART2_RX    GPIO_Pin_2  
  24. 24#define UART2_TX    GPIO_Pin_3  
  25. 25  
  26. 26//拼接过程   
  27. 27#define _UARTX_CORE(uartx)   UART##uartx##_CORE   
  28. 28#define UARTX_CORE(uartx)    _UARTX_CORE(uartx)  
  29. 29  
  30. 30  
  31. 31#define _UARTX_RX(uartx)   UART##uartx##_RX  
  32. 32#define UARTX_RX(uartx)    _UARTX_RX(uartx)   
  33. 33  
  34. 34#define _UARTX_TX(uartx)   UART##uartx##_TX  
  35. 35#define UARTX_TX(uartx)    _UARTX_TX(uartx)  
  36. 36  
  37. 37 
  38. 38int main(int argc, char *argv[]) {  
  39. 39  
  40. 40    //组合标识符的使用   
  41. 41    printf("0x%x\n",UARTX_CORE(UARTX));  
  42. 42    printf("0x%x\n",UARTX_RX(UARTX));  
  43. 43    printf("0x%x\n",UARTX_TX(UARTX));  
  44. 44  
  45. 45    return 1;  
  46. 46} 

编写的思路bug菌在代码中跟大家都标注了,相信大家一眼就能看懂,似乎并没有想象中那么难。

而在前面介绍##的基础知识提过,只要转换宏写得够多,你可以一层套一层,最终获得你想要的标识符,达到修改一个简单的宏即可替换一整套宏的效果。

所以关键还是你要清晰的把拼接变量找出来,bug菌这里仅展示了一个拼接变量,当然多个也是同样没有问题的,跟我们函数传递参数一样,不过这样也会增加整个替换的复杂度,合理利用即可~

最后

好了,今天的内容就分享到这里,我仍然是我,一直没变,觉得有所收获,记得点个赞。

 

责任编辑:庞桂玉 来源: C语言与C++编程
相关推荐

2021-11-18 07:55:03

Reduce验证数组

2020-10-31 09:06:37

C语言编程语言

2022-03-23 08:01:04

Python语言代码

2020-05-15 09:30:12

代码函数语言

2021-01-18 11:27:03

Istio架构云环境

2020-04-07 08:00:02

Redis缓存数据

2020-03-20 08:00:32

代码程序员追求

2020-10-16 09:09:56

代码业务模型

2020-04-14 10:06:20

微服务Netflix语言

2020-07-07 10:55:01

C++C语言代码

2021-04-27 07:52:19

StarterSpring Boot配置

2021-10-11 09:38:46

模型Transformer代码

2022-10-08 00:03:00

Debug技巧调试

2024-01-04 14:16:05

腾讯红黑树Socket

2020-12-09 08:27:48

并发编程编程开发

2020-12-09 15:00:08

编程IT线程

2020-04-30 11:25:32

Insert into数据库索引

2021-07-22 07:50:47

删库系统数据

2021-09-30 18:22:46

VSCode插件API

2019-09-27 09:13:55

Redis内存机制
点赞
收藏

51CTO技术栈公众号