替换文本宏
预处理器支持文本宏替换。还支持类似函数的文本宏替换。
内容 |
[编辑] 语法
#define 标识符 替换列表 (可选) |
(1) | ||||||||
#define 标识符 ( 参数 ) 替换列表 (可选) |
(2) | ||||||||
#define 标识符 ( 参数 , ...) 替换列表 (可选) |
(3) | (自 C++11) | |||||||
#define 标识符 (...) 替换列表 (可选) |
(4) | (自 C++11) | |||||||
#undef 标识符 |
(5) | ||||||||
[编辑] 解释
[编辑] #define 指令
的 #define 指令将 标识符 定义为宏,即指示编译器将大多数后续出现的 标识符 替换为 替换列表,该列表将被进一步处理。例外情况来自 扫描和替换 规则。如果该标识符已被定义为任何类型的宏,则程序将不正确,除非定义相同。
[编辑] 类对象宏
类对象宏将定义的 标识符 的每次出现替换为 替换列表。 的版本 (1) #define 指令的行为完全像那样。
[编辑] 类函数宏
类函数宏将定义的 标识符 的每次出现替换为 替换列表,此外还采用多个参数,这些参数将替换 替换列表 中任何 参数 的对应出现。
类函数宏调用的语法类似于函数调用的语法:宏名称的每次出现后面跟着 ( 作为下一个预处理标记,将引入将被 替换列表 替换的标记序列。该序列以匹配的 ) 标记结束,跳过中间匹配的左括号和右括号对。
对于版本 (2),参数数量必须与宏定义中的参数数量相同。对于版本 (3,4),参数数量不能少于参数数量 (不(自 C++20) 计算 ...
)。否则,程序将不正确。如果标识符不在函数表示法中,即自身后面没有括号,则根本不会被替换。
的版本 (2) #define 指令定义一个简单的类函数宏。
版本 (3) 的 #define 指令定义了一个带有可变数量参数的类似函数的宏。可以使用 __VA_ARGS__
标识符访问附加参数(称为*可变参数*),然后将其替换为提供给要替换标识符的参数。
版本 (4) 的 #define 指令定义了一个带有可变数量参数但没有常规参数的类似函数的宏。只能使用 __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' 对于基本字符集的成员可能为假,则扩展为 1,例如在将 Unicode 用于 wchar_t 的基于 EBCDIC 的系统上 (宏常量) | ||||
__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++ 标准。
DR | 应用于 | 已发布的行为 | 正确行为 |
---|---|---|---|
LWG 294 | C++98 | 包含标准库头文件的翻译单元可能包含 在其他标准库头文件中声明的名称的宏定义 |
禁止 |
P2621R2 | C++23 | 不允许使用通用字符名称 通过标记连接形成 |
允许 |
[编辑] 参见
C++ 文档 for 宏符号索引
| |
C 文档 for 替换文本宏
|