3 min to read
宏的 # 和 ## 運算子、瑣碎規則和預先定義的宏
目錄: C/C++ Macro 宏系列 | CWKSC’s blog | 博客
這一篇來說一些宏的基礎知識、#
和 ##
、一些瑣碎規則和預先定義的宏
▌預處理器
預處理器,負責一些編譯前進行的工作
在 C/C++ 中,一般以 #
開頭的都與預處理器有關
例如: #include
, #define
, #if
, #endif
, #else
, #elif
, #ifdef
, #ifndef
, #error
, #pragma
, #typedef
等等
詳情可參考 Microsoft Docs 的 C/C++ preprocessor reference 和 Preprocessor Directives
▌#
字符串化運算子 (Stringizing operator)
與宏參數配合使用,放在宏參數前面,展開時會在參數兩端加入 "
,使其字串化
#define str(x) #x
str(42) // "42"
#
和參數之間的空白可有可無
#define str(x) # x
前後的空白會被忽略
str( 42 ) // "42"
中間的空白會被壓縮為一個
str( 42 42 ) // "42 42"
註解會被忽略並變為一個空白
str(42/**/42) // "42 42"
會自動添加逸出反斜線字元,對特殊字符進行轉義
利用此特性可以簡化大量需要逸出反斜線的字串
str(the " \ " mean escaped character.)
// "the \" \\ \" mean escaped character."
另外還有常見的檔案路徑,如果我要輸入 D:\User\Documents\C_Test.txt
作為檔案路徑
正常:"D:\\User\\Documents\\C_Test.txt"
宏: str(D:\User\Documents\C_Test.txt)
可以不用考慮要不要加 \
,也可以打少一些字元
▌##
Token 粘貼運算子 (Token-pasting operator)
把兩個 Token 合併為一個
#define merge(a, b) a##b
merge(4, 2) // 42
意義何在?因為識別字 (identifier) 是以空格等等作為分隔
以下的結果,顯然不是我們想要的
#define merge2(a, b) ab
#define merge3(a, b) a b
merge2(4, 2) // ab
merge3(4, 2) // 4 2
##
前後的空格可有可無
#define merge4(a, b) a ## b
merge4(4, 2) // 42
生成的 Token 可用於進一步的宏替換
#define another_marco 42
merge(another, _marco) // [1]
another ## _marco // [2]
another_marco // [3] 處理 ## 完成,準備重掃描
42 // [4] 重掃描完成
▌#
和 ##
阻止另一個宏的展開
在使用了 #
或 ##
的宏中,其作用的宏參數是另一個宏,這將會阻止另一個宏的展開
#define another_marco 42
#define str(x) #x
#define merge(a, b) a##b
str(another_marco) // "another_marco"
merge(another_marco, _meow) // another_marco_meow
把另一個宏作爲參數時,#
和 ##
不會把它展開
#define another_marco 42
#define merge5(a, b) a a ## b
merge5(another_marco, _meow)
another_marco another_marco ## _meow
another_marco another_marco_meow
42 another_marco_meow
不是它作用的宏參數不會受影響
▌預先定義的宏 (Predefined macros)
__FILE__
以字符串常量展開為當前輸入文件的名稱,這是預處理器打開文件的路徑
__LINE__
以十進制整數常量展開為當前行號
__DATE__
當前源文件的編譯日期,格式是 Mmm dd yyyy
的恆長字符串常量
__TIME__
預處理翻譯單元的翻譯時間,格式為 hh:mm:ss
的字符串常量
__STDC__
以十進制整數常量展開,若 1 ,表示編譯器遵循 ISO C 標準,否則,它將變為 0
__STDC_VERSION__
擴展為C Standard的版本號,其形式為一個長整數常量,若支援 C99,數值為 199901L , 若支援 C11,數值為 201112L 。若支援 C17,數值為 201710L
__func__
函式名稱,展開為字符串常量
還有很多其他的宏,可參考 Predefined macros | Microsoft Docs 和 Predefined Macros (The C Preprocessor)
__LINE__
和 __FILE__
常用於錯誤訊息,__func__
也是,不過 C99 才支援
還有一些很實用編譯器擴展,但 C 標準沒有支持,例如 __COUNT__
,從零開始,每次調用數值加一
▌總結
#
字符串化運算子 (Stringizing operator)
- 展開時在參數兩端加入
"
,使其字串化 #
和參數之間的空白可有可無- 前後的空白會被忽略
- 中間的空白會被壓縮為一個
- 註解會被忽略並變為一個空白
- 會自動添加逸出反斜線字元,對特殊字符進行轉義
##
Token 粘貼運算子 (Token-pasting operator)
- 把兩個 Token 合併為一個
##
前後的空格可有可無- 生成的 Token 可用於進一步的宏替換
在使用了 #
或 ##
的宏中,其作用的宏參數是另一個宏,將會阻止另一個宏展開