文本宏的替换

来自cppreference.com
 
 
C++ 语言
 
 

预处理器支持文本宏的替换。还支持函数式的文本宏替换。

目录

[编辑] 语法

#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))时,这个逗号被解释为宏实参的分隔符,并由于实参个数不匹配而导致编译失败。

[编辑] ### 运算符

在函数式宏中,替换列表中标识符签名的 # 运算符,在对这个标识符进行形参替换后将其结果包含在引号中,从而创建一个字符串字面量。此外,预处理器还会添加反斜杠来将其中嵌入的字符串字面量(如果有的话)的引号进行转义,并按需将字符串中的反斜杠变成两个。所有的开头和结尾的空白都被删除,而文本中间的每个空白序列(除了嵌入的字符串字面量中的之外)都被折叠成一个空格。这项操作被称为“字符串化”。如果字符串化后的结果不是有效的字符串字面量,则其行为未定义。

# 出现于 __VA_ARGS__ 前面时,整个展开后的 __VA_ARGS__ 都被引号包含:

#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 文档