替换文本宏
预处理器支持文本宏替换。也支持类函数文本宏替换。
目录 |
[编辑] 语法
#define 标识符 替换列表(可选) |
(1) | ||||||||
#define 标识符( 参数) 替换列表(可选) |
(2) | ||||||||
#define 标识符( 参数, ...) 替换列表(可选) |
(3) | (C++11 起) | |||||||
#define 标识符(...) 替换列表(可选) |
(4) | (C++11 起) | |||||||
#undef 标识符 |
(5) | ||||||||
[编辑] 解释
[编辑] #define 指令
#define
指令将标识符定义为宏,即指示编译器将随后出现的大部分标识符替换为替换列表,替换列表将被进一步处理。例外情况源于扫描和替换规则。如果标识符已经被定义为任何类型的宏,除非定义是相同的,否则程序是非良构的。
[编辑] 对象式宏
对象式宏将每次出现的已定义标识符替换为替换列表。版本 (1) 的 #define
指令的行为完全如此。
[编辑] 类函数宏
类函数宏将每次出现的已定义标识符替换为替换列表,此外还接受一些参数,这些参数随后替换替换列表中任何参数的相应出现。
类函数宏调用的语法类似于函数调用的语法:宏名称的每次出现后跟一个 (
作为下一个预处理记号,引入将被替换列表替换的记号序列。该序列由匹配的 )
记号终止,跳过中间匹配的左右括号对。
对于版本 (2),实参的数量必须与宏定义中形参的数量相同。对于版本 (3,4),实参的数量不得少于形参的数量(不包括 ...)。否则,程序是非良构的。如果标识符不是函数式表示法,即自身后没有括号,则根本不会被替换。
版本 (2) 的 #define
指令定义了一个简单的类函数宏。
版本 (3) 的 #define
指令定义了一个具有可变数量实参的类函数宏。可以使用 __VA_ARGS__
标识符访问附加实参(称为可变实参),然后该标识符被替换为与要替换的标识符一起提供的实参。
版本 (4) 的 #define
指令定义了一个具有可变数量实参但没有常规实参的类函数宏。只能使用 __VA_ARGS__
标识符访问实参(称为可变实参),然后该标识符被替换为与要替换的标识符一起提供的实参。
对于版本 (3,4),替换列表可能包含记号序列 #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)),则逗号被解释为宏实参分隔符,导致由于实参数量不匹配而导致的编译失败。
[编辑] 扫描和替换
- 扫描跟踪它们替换的宏。如果扫描找到与此类宏匹配的文本,则将其标记为“要忽略”(所有扫描都将忽略它)。这可以防止递归。
- 如果扫描找到类函数宏,则在将实参放入替换列表之前会先扫描实参。
#
和##
运算符除外,它们接受未扫描的实参。 - 宏被替换后,将扫描结果文本。
注意,可以定义伪递归宏
#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 起) |
否则,行为是未定义的。
[编辑] # 和 ## 运算符
在类函数宏中,替换列表中标识符之前的 #
运算符对标识符执行形参替换,并将结果括在引号中,从而有效地创建字符串字面量。此外,预处理器添加反斜杠来转义嵌入式字符串字面量(如果有)周围的引号,并在字符串中根据需要加倍反斜杠。所有前导和尾随空格都被删除,文本中间的任何空格序列(但不在嵌入式字符串字面量内部)都折叠为单个空格。此操作称为“字符串化”。如果字符串化的结果不是有效的字符串字面量,则行为是未定义的。
当 #define showlist(...) puts(#__VA_ARGS__) showlist(); // expands to puts("") showlist(1, "x", int); // expands to puts("1, \"x\", int") |
(C++11 起) |
替换列表中任何两个连续标识符之间的 ##
运算符对这两个标识符(首先不进行宏展开)执行形参替换,然后连接结果。此操作称为“连接”或“记号粘贴”。只有一起形成有效记号的记号才可以粘贴:形成更长标识符的标识符,形成数字的数字,或形成 +=
的运算符 +
和 =
。不能通过粘贴 /
和 *
来创建注释,因为在考虑宏替换之前,注释已从文本中删除。如果连接的结果不是有效的记号,则行为是未定义的。
注意:某些编译器提供了一个扩展,允许 ##
出现在逗号之后和 __VA_ARGS__
之前,在这种情况下,当存在可变实参时,##
不执行任何操作,但在不存在可变实参时删除逗号:这使得可以定义诸如 fprintf (stderr, format, ##__VA_ARGS__) 之类的宏。 这也可以使用 __VA_OPT__
以标准方式实现,例如 fprintf (stderr, format __VA_OPT__(, ) __VA_ARGS__)。(C++20 起)
[编辑] #undef 指令
#undef
指令取消定义标识符,即取消先前由 #define
指令对标识符的定义。如果标识符没有关联的宏,则忽略该指令。
[编辑] 预定义宏
以下宏名在每个翻译单元中都是预定义的
__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 for 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 起) |
注释函数局部预定义变量 |
(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 | 应用于 | 已发布行为 | 正确行为 |
---|---|---|---|
CWG 2908 | C++98 | 不清楚 __LINE__ 是否展开为当前的物理行号或当前逻辑行号 |
展开为当前的 物理行号 |
LWG 294 | C++98 | 包含标准库头文件的翻译单元可能包含 定义在其他标准库头文件中声明的名称的宏 |
禁止 |
P2621R2 | C++23 | 通用字符名称是不允许的 通过记号连接形成 |
允许 |
[编辑] 参见
C++ 文档,关于 宏符号索引
| |
C 文档,关于 替换文本宏
|