C语言系列文章之#和##
很久就知道了 # 和 ## ,但是都沒怎么使用,直到最近的項目涉及到需要編寫大量相似的代碼之后才決定嘗試使用 ## 去簡化代碼的書寫。
比如說我的項目需要控制四個通道的電機,四個通道的邏輯控制代碼都是類似的,只是對應的硬件和數據信息不同而已。而我是一個討厭做重復工作的人,所以就想利用 ## 去簡化我的代碼書寫。
就比如說代碼初始化這一塊,總共有四份相似的代碼,如果每一個都要去源碼的位置進行修改,麻煩不說,還有可能忘記修改某部分代碼,導致程序的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
掃碼或長按關注
回復「?加群?」進入技術群聊
? ?
總結
以上是生活随笔為你收集整理的C语言系列文章之#和##的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Activiti6--入门学习--监听器
- 下一篇: 使用boston房价数据进行线性回归分析