名稱空間
變體
操作

文字宏替換

來自 cppreference.com
 
 
C++ 語言
 
 

預處理器支援文字宏替換。還支援函式式文字宏替換。

目錄

[編輯] 語法

#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 可能包含標記序列 __VA_OPT__(content ),如果 __VA_ARGS__ 非空,則替換為 content,否則擴充套件為空。

#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 中識別符號前的 # 運算子對識別符號執行引數替換,並將結果用引號括起來,有效地建立了一個字串字面量。此外,預處理器會新增反斜槓以轉義包圍嵌入式字串字面量(如果有)的引號,並根據需要將字串中的反斜槓加倍。所有前導和尾隨空格都將被刪除,並且文字中間的任何空格序列(但不包括嵌入式字串字面量內部的空格)都將合併為一個空格。此操作稱為“字串化”。如果字串化的結果不是有效的字串字面量,則行為未定義。

# 出現在 __VA_ARGS__ 之前時,整個展開的 __VA_ARGS__ 都用引號括起來。

#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++ 標準版本,展開為值
  • 199711L(直到 C++11)
  • 201103L(C++11)
  • 201402L(C++14)
  • 201703L(C++17)
  • 202002L(C++20),或
  • 202302L(C++23)
    (宏常量)
__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)

展開為形式為 yyyymmL 的整數常量,如果 wchar_t 使用 Unicode,則日期表示支援的最新 Unicode 修訂版

(直至 C++23)

實現定義的值,如果存在

(C++23 起)

(宏常量)
__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 文件 關於 文字宏替換