命名空间
变体
操作

文本宏替换

来自 cppreference.cn
 
 
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 文档 关于 文本宏替换