文本宏的替换
预处理器支持文本宏的替换。还支持函数式的文本宏替换。
目录 |
[编辑] 语法
#define 标识符 替换列表(可选)
|
(1) | ||||||||
#define 标识符( 参数列表 ) 替换列表
|
(2) | ||||||||
#define 标识符( 参数列表, ... ) 替换列表
|
(3) | (C++11 起) | |||||||
#define 标识符( ... ) 替换列表
|
(4) | (C++11 起) | |||||||
#undef 标识符
|
(5) | ||||||||
[编辑] 解释
[编辑] #define
指令
#define
指令将标识符定义为一个宏,指示编译器将后续出现的所有标识符都替换为替换列表,后者还可以进行进一步的处理。当这个标识符已被定义为任何种类的宏时,除非其定义相同,否则程序非良构。
[编辑] 对象式的宏
对象式的宏,将其定义的标识符的每次出现都替换为其替换列表。版本 (1) 的 #define
指令的行为正是如此。
[编辑] 函数式的宏
函数式的宏,将其定义的标识符的每次出现都替换为其替换列表,还会接受一些实参,以之替换掉其替换列表中所出现的任何其所对应的形参。
函数式的宏的调用语法和函数调用的语法相似:每当出现这个宏名后面跟着 ( 作为其下一个预处理记号,就引入了一系列将被替换为其替换列表的记号。这个序列以一个匹配的 ) 记号结束,跳过中间交错的相互匹配的左右圆括号对。
实参的个数必须与宏定义的参数(形参)的个数相同,否则程序非良构。当这个标识符并不在函数语法中时,比如它后面没有括号时,它并不会被替换。
版本 (2) 的 #define
指令定义了一个简单的函数式宏。
版本 (3) 的 #define
指令定义了一个带有可变数目的参数的函数式宏。可以使用 __VA_ARGS__
标识符来访问额外的实参,它将被与所要替换的标识符一同提供的各个实参所替换。
版本 (4) 的 #define
指令定义了一个带有可变数目的参数但没有普通参数的函数式宏。只能使用 __VA_ARGS__
标识符来访问其各个参数,它将被与所要替换的标识符一同提供的各个实参所替换。
注:当函数式宏的实参中包含未被匹配的左右括号所保护的逗号(最常见的情况是在类型名中,比如 assert(std::is_same_v<int, int>); 或者 BOOST_FOREACH(std::pair<int,int> p, m))时,这个逗号被解释为宏实参的分隔符,并由于实参个数不匹配而导致编译失败。
[编辑] #
和 ##
运算符
在函数式宏中,替换列表中标识符签名的 #
运算符,在对这个标识符进行形参替换后将其结果包含在引号中,从而创建一个字符串字面量。此外,预处理器还会添加反斜杠来将其中嵌入的字符串字面量(如果有的话)的引号进行转义,并按需将字符串中的反斜杠变成两个。所有的开头和结尾的空白都被删除,而文本中间的每个空白序列(除了嵌入的字符串字面量中的之外)都被折叠成一个空格。这项操作被称为“字符串化”。如果字符串化后的结果不是有效的字符串字面量,则其行为未定义。
当 #define showlist(...) puts(#__VA_ARGS__) showlist(); // 展开为 puts("") showlist(1, "x", int); // 展开为 puts("1, \"x\", int") |
(C++11 起) |
替换列表中的任何两个相邻的标识符之间的 ##
运算符,在对这两个标识符进行参数替换(但并不首先进行宏展开)之后将其结果拼接到一起。这项操作被称为“拼接”或“记号粘贴”。只有能够形成一个有效记号的记号可以粘贴到一起:诸如构成更长标识符的两个标识符,构成数值的数字,或者构成 +=
的运算符 +
和 =
。由于在考虑宏替换之前就已经将注释从文本中移除了,因而不能通过粘贴 /
和 *
来创建注释。如果拼接的结果不是有效的记号,则其行为未定义。
注:一些编译器提供一种扩展,允许在逗号后面和 __VA_ARGS__ 前面使用 ##,这种情况下当 __VA_ARGS__ 不为空时没有效果,但当 __VA_ARGS__ 为空时将删除这个逗号,这使得可以定义这样的宏:fprintf (stderr, format, ##__VA_ARGS__)
[编辑] #undef
指令
#undef
指令取消定义该标识符,即取消掉之前以 #define
指令对该标识符的定义。如果这个标识符并没有相关的宏,则忽略该指令。
[编辑] 预定义的宏
下列宏名在任何翻译单元中都有预定义。
__cplusplus |
代表所使用的 C++ 标准的版本,扩展为值 199711L(C++11 前),201103L(C++11),或 201402L(C++14) (宏常量) |
__STDC_HOSTED__ (C++11) |
当实现有宿主(在操作系统下运行)时扩展为整数常量 1,当为自立实现(没有操作系统)时为 0 (宏常量) |
__FILE__ |
扩展为当前文件的名字,字符串字面量,可以通过 #line 指令改变 (宏常量) |
__LINE__ |
扩展为源文件行号,整数常量,可以通过 #line 指令改变 (宏常量) |
__DATE__ |
扩展为翻译的日期,形式为 "Mmm dd yyyy" 的字符串字面量。其中月份的名字与 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) (宏常量) |
下列的其他宏名可能由实现进行预定义。
__STDC__ |
由实现定义的值,如果存在,通常用于标明对 C 的遵从性 (宏常量) |
__STDC_VERSION__ (C++11) |
由实现定义的值,如果存在 (宏常量) |
__STDC_ISO_10646__ (C++11) |
当 wchar_t 使用 Unicode 时扩展为形式为 yyyymmL 的整数常量,这个日期标明所支持的最新 Unicode 版本。 (宏常量) |
__STDC_MB_MIGHT_NEQ_WC__ (C++11) |
当基本字符集的宽字符编码可能与其窄字符编码不同时扩展为 1 (宏常量) |
__STDCPP_STRICT_POINTER_SAFETY__ (C++11) |
当实现具有严格的 std::pointer_safety 时扩展为 1 (宏常量) |
__STDCPP_THREADS__ (C++11) |
当程序可以具有多个执行线程时扩展为 1 (宏常量) |
这些宏(除 __FILE__
和 __LINE__
之外)的值在翻译单元中保持不变。试图重新定义或者取消这些宏的结果导致未定义的行为。
注:在每一个函数体的作用域中,有一个名为 __func__(C++11 起) 的特殊的函数局部预定义变量,它被定义为静态字符数组,以由实现定义的格式保持这个函数的名字。它并不是预处理器宏,但它可以和 __FILE__
和 __LINE__
一起使用,比如 assert。
[编辑] 示例
#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 << #a << '\n' 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); // 注意这里没有引号 }
输出:
abcd: 12 fff: 2 qqq: 23 34 million
[编辑] 另请参阅
文本宏的替换的 C 文档
|