文字宏替換
預處理器支援文字宏替換。還支援函式式文字宏替換。
目錄 |
[編輯] 語法
#define identifier replacement-list (可選) |
(1) | ||||||||
#define identifier ( parameters ) replacement-list (可選) |
(2) | ||||||||
#define identifier ( parameters , ...) replacement-list (可選) |
(3) | (C++11 起) | |||||||
#define identifier (...) replacement-list (可選) |
(4) | (C++11 起) | |||||||
#undef identifier |
(5) | ||||||||
[編輯] 解釋
[編輯] #define 指令
#define 指令將 identifier 定義為宏,即指示編譯器將 identifier 的大多數後續出現替換為 replacement-list,後者將進一步處理。例外情況源於掃描和替換規則。如果識別符號已被定義為任何型別的宏,除非定義相同,否則程式格式錯誤。
[編輯] 類物件宏
類物件宏將每個出現的已定義 identifier 替換為 replacement-list。#define 指令的版本 (1) 行為完全相同。
[編輯] 類函式宏
類函式宏將每個出現的已定義 identifier 替換為 replacement-list,此外還帶有一些引數,這些引數會替換 replacement-list 中任何 parameters 的相應出現。
函式式宏呼叫的語法與函式呼叫的語法相似:每個宏名稱例項後跟一個 ( 作為下一個預處理標記,引入將被 replacement-list 替換的標記序列。序列由匹配的 ) 標記終止,跳過其中匹配的左右括號對。
對於版本 (2),引數數量必須與宏定義中的引數數量相同。對於版本 (3,4),引數數量不得少於引數數量(不(C++20 起) 計算 ...
)。否則程式格式錯誤。如果識別符號不是函式式表示法,即其後沒有括號,則根本不會被替換。
#define 指令的版本 (2) 定義了一個簡單的函式式宏。
#define 指令的版本 (3) 定義了一個帶有可變數量引數的函式式宏。附加引數(稱為可變引數)可以使用 __VA_ARGS__
識別符號訪問,該識別符號隨後被替換為與要替換的識別符號一起提供的引數。
#define 指令的版本 (4) 定義了一個帶有可變數量引數但沒有常規引數的函式式宏。這些引數(稱為可變引數)只能使用 __VA_ARGS__
識別符號訪問,該識別符號隨後被替換為與要替換的識別符號一起提供的引數。
對於版本 (3,4),replacement-list 可能包含標記序列 #define F(...) f(0 __VA_OPT__(,) __VA_ARGS__) F(a, b, c) // replaced by f(0, a, b, c) F() // replaced by f(0) #define G(X, ...) f(0, X __VA_OPT__(,) __VA_ARGS__) G(a, b, c) // replaced by f(0, a, b, c) G(a, ) // replaced by f(0, a) G(a) // replaced by f(0, a) #define SDEF(sname, ...) S sname __VA_OPT__(= { __VA_ARGS__ }) SDEF(foo); // replaced by S foo; SDEF(bar, 1, 2); // replaced by S bar = { 1, 2 }; |
(C++20 起) |
注意:如果函式式宏的引數包含未受匹配的左右括號對保護的逗號(最常見於模板引數列表,如 assert(std::is_same_v<int, int>); 或 BOOST_FOREACH(std::pair<int, int> p, m)),則逗號被解釋為宏引數分隔符,導致由於引數數量不匹配而編譯失敗。
[編輯] 掃描與替換
- 掃描會跟蹤已替換的宏。如果掃描發現與此類宏匹配的文字,則將其標記為“忽略”(所有掃描都會忽略它)。這可以防止遞迴。
- 如果掃描發現類函式宏,則在將其放入 replacement-list 之前掃描引數。除了 # 和 ## 運算子直接獲取引數而不進行掃描。
- 宏替換後,結果文字將再次掃描。
請注意,可以定義偽遞迴宏
#define EMPTY #define SCAN(x) x #define EXAMPLE_() EXAMPLE #define EXAMPLE(n) EXAMPLE_ EMPTY()(n-1) (n) EXAMPLE(5) SCAN(EXAMPLE(5))
輸出
EXAMPLE_ ()(5 -1) (5) EXAMPLE_ ()(5 -1 -1) (5 -1) (5)
[編輯] 保留宏名稱
包含標準庫標頭檔案的翻譯單元不得 #define 或 #undef 在任何標準庫標頭檔案中宣告的名稱。
使用標準庫任何部分的翻譯單元不允許 #define 或 #undef 在詞法上與以下內容相同的名稱
|
(C++11 起) |
否則,行為未定義。
[編輯] # 和 ## 運算子
在類函式宏中,replacement-list 中識別符號前的 # 運算子對識別符號執行引數替換,並將結果用引號括起來,有效地建立了一個字串字面量。此外,預處理器會新增反斜槓以轉義包圍嵌入式字串字面量(如果有)的引號,並根據需要將字串中的反斜槓加倍。所有前導和尾隨空格都將被刪除,並且文字中間的任何空格序列(但不包括嵌入式字串字面量內部的空格)都將合併為一個空格。此操作稱為“字串化”。如果字串化的結果不是有效的字串字面量,則行為未定義。
當 # 出現在 #define showlist(...) puts(#__VA_ARGS__) showlist(); // expands to puts("") showlist(1, "x", int); // expands to puts("1, \"x\", int") |
(C++11 起) |
replacement-list 中任意兩個連續識別符號之間的 ## 運算子對這兩個識別符號(它們不會首先進行宏展開)執行引數替換,然後將結果連線起來。此操作稱為“連線”或“標記貼上”。只有能夠一起形成有效標記的標記才能被貼上:形成更長識別符號的識別符號,形成數字的數字,或形成 +=
的運算子 +
和 =
。不能透過貼上 /
和 *
來建立註釋,因為註釋在考慮宏替換之前就已從文字中刪除。如果連線的結果不是有效的標記,則行為未定義。
注意:有些編譯器提供了一個擴充套件,允許 ## 出現在逗號之後和 __VA_ARGS__
之前,在這種情況下,當存在可變引數時,## 不執行任何操作,但當不存在可變引數時,它會刪除逗號:這使得可以定義宏,例如 fprintf (stderr, format, ##__VA_ARGS__)。這也可以透過使用 __VA_OPT__
以標準方式實現,例如 fprintf (stderr, format __VA_OPT__(, ) __VA_ARGS__)。(C++20 起)
[編輯] #undef 指令
#undef 指令取消定義 identifier,即取消 #define 指令對 identifier 的先前定義。如果識別符號沒有關聯的宏,則忽略該指令。
[編輯] 預定義宏
以下宏名稱在每個翻譯單元中都是預定義的
__cplusplus |
表示正在使用的 C++ 標準版本,展開為值
|
__STDC_HOSTED__ (C++11) |
如果實現是託管的(在作業系統下執行),則展開為整數常量 1,如果獨立(沒有作業系統執行),則展開為 0 (宏常量) |
__FILE__ |
展開為當前檔案的名稱,作為字元字串字面量,可以透過 #line 指令更改(宏常量) |
__LINE__ |
展開為當前物理原始碼行的行號,一個整數常量,可以透過 #line 指令更改(宏常量) |
__DATE__ |
展開為翻譯日期,一個形式為 "Mmm dd yyyy" 的字元字串字面量。如果月份中的日期小於 10,則 "dd" 的第一個字元是一個空格。月份的名稱如同由 std::asctime() 生成 (宏常量) |
__TIME__ |
展開為翻譯時間,一個形式為 "hh:mm:ss" 的字元字串字面量 (宏常量) |
__STDCPP_DEFAULT_NEW_ALIGNMENT__ (C++17) |
展開為 std::size_t 字面量,其值是呼叫不感知對齊的 operator new 所保證的對齊(較大的對齊將傳遞給感知對齊的過載,例如 operator new(std::size_t, std::align_val_t)) (宏常量) |
__STDCPP_BFLOAT16_T____STDCPP_FLOAT16_T____STDCPP_FLOAT32_T____STDCPP_FLOAT64_T____STDCPP_FLOAT128_T__ (C++23) |
當且僅當實現支援相應的擴充套件浮點型別時,展開為 1 (宏常量) |
以下附加宏名稱可能由實現預定義
__STDC__ |
實現定義的值,如果存在,通常用於指示 C 標準符合性 (宏常量) | ||||
__STDC_VERSION__ (C++11) |
實現定義的值,如果存在 (宏常量) | ||||
__STDC_ISO_10646__ (C++11) |
(宏常量) | ||||
__STDC_MB_MIGHT_NEQ_WC__ (C++11) |
如果對於基本字元集的成員,'x' == L'x' 可能為假,例如在使用 Unicode 作為 wchar_t 的基於 EBCDIC 的系統上,則展開為 1 (宏常量) | ||||
__STDCPP_THREADS__ (C++11) |
如果程式可以有多個執行執行緒,則展開為 1 (宏常量) |
__STDCPP_STRICT_POINTER_SAFETY__ (C++11)(在 C++23 中移除) |
如果實現具有嚴格的 std::pointer_safety,則展開為 1 (宏常量) |
這些宏的值(除了 __FILE__
和 __LINE__
)在整個翻譯單元中保持不變。嘗試重新定義或取消定義這些宏會導致未定義行為。
語言特性測試宏標準定義了一組預處理器宏,對應於 C++11 或更高版本中引入的 C++ 語言特性。它們旨在作為一種簡單便攜的方式來檢測所述特性的存在。 有關詳細資訊,請參閱特性測試。 |
(C++20 起) |
注意函式區域性預定義變數 __func__ 不是預定義宏,但它通常與 __FILE__ 和 __LINE__ 一起使用,例如由 assert。 |
(C++11 起) |
[編輯] 示例
#include <iostream> // Make function factory and use it #define FUNCTION(name, a) int fun_##name() { return a; } FUNCTION(abcd, 12) FUNCTION(fff, 2) FUNCTION(qqq, 23) #undef FUNCTION #define FUNCTION 34 #define OUTPUT(a) std::cout << "output: " #a << '\n' // Using a macro in the definition of a later macro #define WORD "Hello " #define OUTER(...) WORD #__VA_ARGS__ int main() { std::cout << "abcd: " << fun_abcd() << '\n'; std::cout << "fff: " << fun_fff() << '\n'; std::cout << "qqq: " << fun_qqq() << '\n'; std::cout << FUNCTION << '\n'; OUTPUT(million); //note the lack of quotes std::cout << OUTER(World) << '\n'; std::cout << OUTER(WORD World) << '\n'; }
輸出
abcd: 12 fff: 2 qqq: 23 34 output: million Hello World Hello WORD World
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
CWG 2908 | C++98 | 不清楚 __LINE__ 是展開為當前物理行號還是當前邏輯行號 |
展開為當前 物理行號 |
LWG 294 | C++98 | 包含標準庫標頭檔案的翻譯單元可能包含 定義在其他標準庫標頭檔案中宣告的名稱的宏 |
已禁止 |
P2621R2 | C++23 | 通用字元名稱不允許 透過標記連線形成 |
允許 |
[編輯] 另請參閱
C++文件關於宏符號索引
| |
C 文件 關於 文字宏替換
|