很久就知道了 # 和 ## ,但是都没怎么使用,直到最近的项目涉及到需要编写大量相似的代码之后才决定尝试使用 ## 去简化代码的书写。
比如说我的项目需要控制四个通道的电机,四个通道的逻辑控制代码都是类似的,只是对应的硬件和数据信息不同而已。而我是一个讨厌做重复工作的人,所以就想利用 ## 去简化我的代码书写。
就比如说代码初始化这一块,总共有四份相似的代码,如果每一个都要去源码的位置进行修改,麻烦不说,还有可能忘记修改某部分代码,导致程序的bug。对此比较好的方式就是使用宏定义,将所有需要修改的东西整合在一个位置,这样进行修改的时候就能一次性修改完成,就不用在源码中到处找哪里需要修改了。而为了更好的配合这种修改方式,使用 ## 就很有必要了。
下面看看在我项目中的应用吧!
以两个通道为例:
.c文件
.h文件
使用 ## 的宏定义
当我需要初始化新的通道的时候,在源码位置只要修改一个地方就行就行,而头文件部分的修改不管采用哪种方式都是必不可少的,这没什么好说的。而当采用 ## 的方式编写代码,你后期如果需要增加一个通道的初始化代码,你的工作就是复制前面通道的代码,然后修改源代码的那一个宏定义,最后在头文件中统一修改引脚信息就行了。
而实现这种方式的关键就是 ##,它的作用就是将你写的标志符进行拼接,相当于自动实现代码的编写了。
上面那么多话如果没有实际使用经验其实不是特别好理解,现在以比较简单的方式进行说明。
在对引脚进行初始化的时候,一般需要端口信息、引脚信息(当然还需要对应的时钟信息,暂时不考虑)
使用库函数进行开发时一般是采用以下方式进行引脚初始化:
只有单个引脚的时候,只要修改这两个地方就能实现一个引脚的初始化,还是比较简单的。但是如果有多个相同模式的引脚需要进行这样的初始化,就会显得很麻烦。而且一旦硬件修改了,就可能会有很多地方需要修改,很可能就忘记某个地方的修改了。所以为了方便的对需要修改的地方进行修改,就很有必要使用一个标志符去代替具体的信息。所以一般就会采用宏定义的方式:
这样一旦后期要改了,因为已经将所有要修改的地方都放在一块了,那么即使过去了很久,也能很快的根据硬件开发板进行修改。而不会说忘记修改哪部分代码了。
但这是比较常规的写法,在没大量相似代码的情况下还是很方便的,但是如果说我有两、三个SPI_MISO引脚呢?我怎样才能更快的写出我的代码呢?就是使用 ## 进行拼接了。
首先你要确定你变化的是什么,不变的又是什么。变化的就是不同的引脚,不变的就是都是开漏复用输出等配置。
现在看看如何实现。
首先看看常规的操作:
当修改完这两个地方时,第二个SPI的引脚就算是配置完成了。但是万一你忘记修改了一个地方呢?所以,现在想办法只修改一个地方,从而达到修改两个地方的目的(多个地方修改也是同理)。
这就是 ## 的好处了。
现在讲讲如何正确使用 ##。
和宏定义一样,遇到相同字符就会被替换,比如:
这里有两个spix,所以两个都被替换了成了1,然后通过 ## 进行拼接。这个可以通过编译看出来最终拼出来的是什么(在拼出问题的情况下才能看到。当然你也可以直接让鼠标指针选择被拼接的地方也能看出来,这样就不用进行编译了)。
如果说你传入的参数不是 1,而是一个宏定义(就像SPIx是一个宏定义),那么你就需要加一个外壳进行二次替换才行。
很多时候我们要求被替换的东西加入一对括号 (),但使用 ## 进行拼接的时候却最好不要,否则可能拼接失败(如果拼接失败,看看是否是因为这个):
如果有多个变化的地方,那么宏中传入多个参数就可以了,类似这样的:
如果拼接失败,请先确保你传入的参数是 MISO,而不是 MOSI:
以上是比较常用的使用方式,但有时候我们真的很懒,两个没有直接联系但有间接联系的参数我也想通过某种方式进行拼接:
可以看到,在其他宏没有改变的情况下,虽然我传入的参数是Sx为4,4可以说和最终的拼接 SPI1_MISO_GPIO_Pin_x 没有任何关系,但是通过再次拼接参数宏的方式硬是让最后的拼接结果变成了 SPI1_MISO_GPIO_Pin_x。
现在简单分析一下拼接的过程:
首先编译器碰到了 SIPx(Sx),然后 Sx是 4,通过中间转换宏:
#define SPIx(x) _SPIx(x)
变成了 _SPI(4),然后通过宏:
#define _SPIx(x) SPI##x##_MISO
拼接成了 SPI4_MISO,也就是:
SPIx_MISO_GPIO_Pin_x(SPI4_MISO)
因为SPI4_MISO也是一个宏,所以继续通过转换宏:
#define SPIx_MISO_GPIO_Pin_x(spix) _SPIx_MISO_GPIO_Pin_x(spix)
在这里 SPI4_MISO 被替换成了 1,也就变成了这个:
_SPIx_MISO_GPIO_Pin_x(1)
但还没完,继续替换,通过下面的宏:
#define _SPIx_MISO_GPIO_Pin_x(spix) SPI##spix##_MISO_GPIO_Pin_x
拼接为:
SPI1_MISO_GPIO_Pin_x
但是它还是一个宏,所以 SPI1_MISO_GPIO_Pin_x继续被替换为:
GPIO_Pin_4
这就算替换完了吗?非也,实际上 GPIO_Pin_4 也是一个宏:
所以继续替换,最终替换成了:
((uint16_t)0x0010)
是不是感觉好麻烦啊,为了一个 ((uint16_t)0x0010) 不知道走了多少弯路,不过总算是替换完了。你一个新手确实觉得难,但理解之后也就不难了。而高手写代码时写这么多的宏,不会觉得累吗?其实不是的,相比于不写宏造成的后果,还是写宏来的简单一些,高手经历的更多,当然也就考虑的更多,他们既然选择写宏,就有他们的道理,等新手有足够的经验之后,也就明白了写宏的用意,也就乐意写宏了。
以上就是我使用 ## 时遇到的一些问题。要理解 ## 其实不难,多想多尝试就可以了。现在你从头开始看看吧,或许会有不一样的理解。
现在讲讲 #。这个比较简单一点,鱼鹰也没怎么用过,就简单介绍一下吧。
1、 使用 # 让传入的宏参数变成一个字符串:
#define STRING(x) #x
则STRING (1+1) 相当于 “1+1”,即把 1+1 变成了可以放在数组中的数据了。
2、 使用 #@ ,让参数变成字符
#define CHAR(x) #@x
则CHAR (a) 即‘a’,B(1)即’1’这就变成了可以把它放入变量中的字符了。但B(abc)
却不甚有效。这是因为 abc不是一个字符。
-----------------2018/12/16 Osprey
看以上示例,包含 ## 的宏既可以传入字符,也可以传入你需要的参数。可以根据你的需要设计所需的宏。
----------更新 2019/01/07 Osprey
扫码或长按关注
回复「 加群 」进入技术群聊