预处理器支持文本宏替换。亦支持仿函数文本宏替换。

语法

#define 标识符 替换列表(可选) (1)
#define 标识符( 形参 ) 替换列表(可选) (2)
#define 标识符( 形参, … ) 替换列表(可选) (3) (C++11 起)
#define 标识符( … ) 替换列表(可选) (4) (C++11 起)
#undef 标识符 (5)

解释

#define 指令

#define 指令将 标识符 定义为宏,即指示编译器以将其后出现的所有 标识符 都替换为 替换列表,而它也可以被进一步处理。若该标识符已被定义为任何类型的宏,则除非这些定义都相同,否则程序非良构。

仿对象宏

仿对象宏(object-like)以 替换列表 替换每次出现的被定义 标识符。#define 指令的版本 (1) 准确表现如此。

仿函数宏

仿函数宏(function-like)以 替换列表 替换每次出现的被定义 标识符,可选地接受一定量的实参,它们随即替换掉 替换列表 中出现的任何对应的 形参。

仿函数宏语法类似函数调用语法:每个宏名实例后随一个 ( 作为下个预处理记号,所引入的记号序列将被替换为 替换列表。该序列以匹配的 ) 记号终止,跳过中间的匹配左右括号对。

对于版本 (2),实参数量必须与宏定义中的形参数量相同。对于版本 (3,4) ,实参数量必须多于 (C++20 前)不少于 (C++20 起)形参数量(不计 )。否则程序非良构。若标识符未使用函数写法,即它自身之后无括号,则它完全不被替换。

#define 指令的版本 (2) 定义简单仿函数宏。

#define 指令的版本 (3) 定义有可变数量实参的仿函数宏。额外的实参(是谓可变实参)可用 VA_ARGS 标识符访问,它会被与要被替换的标识符一起提供的实参替换。

#define 指令的版本 (4) 定义有可变数量实参的仿函数宏,但无常规实参。额外的实参(是谓可变实参)只能用 VA_ARGS 标识符访问,它会被与要被替换的标识符一起提供的实参替换。


对于版本 (3,4),替换列表 可以含有记号序列“VA_OPT ( 内容 )”,若 VA_ARGS 非空,则它会被 内容 替换,否则不展开成任何内容。




  1. #define F(…) f(0 VA_OPT(,) VA_ARGS)
    #define G(X, …) f(0, X VA_OPT(,) VA_ARGS)
    #define SDEF(sname, …) S sname VA_OPT(= { VA_ARGS })
    F(a, b, c) // 替换为 f(0, a, b, c)
    F() // 替换为 f(0)
    G(a, b, c) // 替换为 f(0, a, b, c)
    G(a, ) // 替换为 f(0, a)
    G(a) // 替换为 f(0, a)
    SDEF(foo); // 替换为 S foo;
    SDEF(bar, 1, 2); // 替换为 S bar = { 1, 2 };



(C++20 起)

注意:若仿函数宏的实参中包含不为匹配的左右括号对所保护的逗号(最常出现于模板实参列表中,如 assert(std::is_same_v<int, int>); 或 BOOST_FOREACH(std::pair<int,int> p, m)),则逗号被解释成宏实参分隔符,并造成由于实参数量不匹配所致的编译失败。

保留宏名

包含标准库头文件的翻译单元不可 #define#undef 声明于任何标准库头文件中所声明的名字。

使用标准库任何部分的翻译单元不可 #define#undef 词法上等同于下列内容的名称:



- 有特殊含义的标识符
- 任何标准属性记号
(C++11 起)

除了可定义 likelyunlikely 为仿函数宏。
(C++20 起)

否则,行为未定义。

# 与 ## 运算符

仿函数宏中,替换列表 中放在标识符前的 # 运算符,使标识符运行形参替换,并将其结果以引号包围,实际上创建一个字符串字面量。另外,预处理器为内嵌的字符串字面量(若它存在)外围的引号添加反斜杠以进行转义,并按需要双写字符串中的反斜杠。移除所有前导和尾随空白符,并将文本中间(但非内嵌字符串字面量中间)的任何空白符序列缩减成单个空格。此操作被称为“字符串化”,若字符串化的结果不是合法的字符串字面量,则行为未定义。


# 出现于 VA_ARGS 之前时,展开后的 VA_ARGS 整体被包在引号中:




  1. #define showlist(…) puts(#VA_ARGS)
    showlist(); // 展开成 puts("")
    showlist(1, "x", int); // 展开成 puts("1, \"x\", int")



(C++11 起)

替换列表 中任何两个相继标识符之间的 ## 运算符,使二个标识符(首先未被宏展开)上运行形参替换,然后将结果进行拼接。此操作被称为“拼接”或“记号粘贴”。只有一同组成合法记号的记号才可以粘贴:如组成更长标识符的标识符、组成数字的数字位,或组成 += 的运算符 +=。不能通过粘贴 /* 来创建注释,因为在考虑文本宏替换前,注释就已被移除了。若连接的结果不是合法记号,则行为未定义。

注意:一些编译器提供了一项扩展,允许 ## 出现于逗号后及 VA_ARGS 前,此情况下 ## 在存在可变实参时不做任何事,但在不存在可变实参时移除逗号:这使得可以定义如 fprintf (stderr, format, ##VA_ARGS) 这样的宏。

#undef 指令

#undef 指令取消定义 标识符,即取消 #define 指令所作的 标识符 定义。若标识符未关联到宏,则忽略该指令。

预定义宏

下列宏名已预定义于每个翻译单元中。

实现可能预定义下列其他的宏名。

这些宏的值(除了 FILELINE),在翻译单元间保持为常量。试图重定义或取消定义这些宏导致未定义行为。

注意:在每个函数体的作用域内部,都有一个名为 func 的特殊的函数局域预定义变量(C++11 起),定义为一个持有具有实现定义格式的函数名的静态字符数组。它不是预处理器宏,但它与 FILELINE 一起使用,例如 assert



#### 语言功能特性测试宏


下列宏预定义于每个翻译单元。


本节未完成原因:在对应功能特性中描述

(C++20 起)

示例

运行此代码

  1. #include <iostream>
  2.  
  3. // 制造函数工厂并使用它
  4. #define FUNCTION(name, a) int fun_##name() { return a;}
  5.  
  6. FUNCTION(abcd, 12)
  7. FUNCTION(fff, 2)
  8. FUNCTION(qqq, 23)
  9.  
  10. #undef FUNCTION
  11. #define FUNCTION 34
  12. #define OUTPUT(a) std::cout << "output: " #a << '\n'
  13.  
  14. int main()
  15. {
  16. std::cout << "abcd: " << fun_abcd() << '\n';
  17. std::cout << "fff: " << fun_fff() << '\n';
  18. std::cout << "qqq: " << fun_qqq() << '\n';
  19. std::cout << FUNCTION << '\n';
  20. OUTPUT(million); // 注意缺少引号
  21. }

输出:

  1. abcd: 12
  2. fff: 2
  3. qqq: 23
  4. 34
  5. output: million