文本宏替换
预处理器支持文本宏替换。还支持函数式文本宏替换。
目录 |
[编辑] 语法
#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 文档 关于 文本宏替换
|