C语言参悟-文本替换宏
一、概述
这里记录一下,C,C++中预处理器中的文本宏替换和仿函数文本宏替换。宏替换用的非常多,有非常多的优点。
(1) 方便程序的修改
因为宏的本质就是替换,我们只需要改一处,其他的宏修改也会被同时修改好,这就便于维护管理。
(2) 提高程序的运行效率
使用带参数的宏定义可完成函数调用的功能,又能减少系统开销,提高运行效率有点内联函数的味道,但是如果是复杂的宏。还是用函数好点儿。
二、语法
c
#define 标识符 替换列表(可选) (1)
#define 标识符( 形参 ) 替换列表(可选) (2)
#define 标识符( 形参, ... ) 替换列表(可选) (3) (C++11 起)
#define 标识符( ... ) 替换列表(可选) (4) (C++11 起)
#undef 标识符 (5)
#define 指令将 标识符 定义为宏,即指示编译器以将其后出现的所有 标识符 都替换为 替换列表,而它也可以被进一步处理
cpp
#define PI 3.1425926
仿对象宏 : 将 替换列表 替换每次出现的被定义 标识符。#define 指令的版本 (1) 准确表现如此。
仿函数宏 : 将 替换列表 替换每次出现的被定义 标识符,可选地接受一定量的实参,它们随即替换掉 替换列表 中出现的任何对应的 形参。
仿函数宏语法类似函数调用语法:每个宏名实例后随一个 ( 作为下个预处理记号,所引入的记号序列将被替换为 替换列表。该序列以匹配的 ) 记号终止,跳过中间的匹配左右括号对。
例如:
cpp
#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 };
1. # 与 ## 运算符
1. # 运算符号
仿函数宏中,如果替换列表 中一个标识符前有 # 运算符,那么该标识符在运行形参替换的基础上以引号包围,实际上创建一个字符串字面量,也就是把宏参数替换为字符串的形式。
另外,预处理器为内嵌的字符串字面量(如果存在)外围的引号添加反斜杠以进行转义,并按需要双写字符串中的反斜杠。移除所有前导和尾随空白符,并将文本中间(除内嵌字符串字面量中间外)的任何空白符序列缩减成单个空格。此操作被称为"字符串化",如果字符串化的结果不是合法的字符串字面量,那么行为未定义。
cpp
#define showlist(...) puts(#__VA_ARGS__)
showlist(); // 展开成 puts("")
showlist(1, "x", int); // 展开成 puts("1, \"x\", int")
2. ## 运算符
如果替换列表 中任何两个相继标识符之间有 ## 运算符,那么这两个标识符(首先未被宏展开)在运行形参替换的基础上将结果进行拼接。此操作被称为"拼接"或"记号粘贴"。只有一同组成合法记号的记号才可以粘贴:如组成更长标识符的标识符、组成数字的数字位,或组成 += 的运算符 + 和 =。
cpp
#define Conn(x,y) x##y
cpp
int n = Conn(123,456); /* 结果就是n=123456;*/
char* str = Conn("asdf", "adf"); /*结果就是 str = "asdfadf";*/
三、预定义宏
这里记录一下常用的宏,
宏名 | 含义 |
---|---|
__cplusplus | 代表所用的 C++ 标准版本,值为:199711L(C++11 前),201103L(C++11),201402L(C++14),201703L(C++17),202002L(C++20),202302L(C++23) |
FILE | 展开成当前文件名,作为字符串字面量,可用 #line 指令更改 |
LINE | 展开成源文件行号,整数常量,可用 #line 指令更改 |
DATE | 展开成翻译日期,形式为 "Mmm dd yyyy" 的字符串。如果月中日期数小于 10 则 "dd" 的首字符为空格。月份名如同以 std::asctime() 生成 |
TIME | 展开开成翻译时间,形式为 "hh:mm:ss" 的字符串字面量 |
就像 __cplusplus 可以去区分是否是C++还是C语言的,FILE、LINE 可以输出日志去定位到某一行代码
一个简单的例子如下:
cpp
// 定义
#define INFO "info"
#define DEBUG "debug"
#define LOG(RANK, LOGDATA) \
std::cout<<"["<<RANK<<" "<<__DATE__<<" "<<__TIME__<<"] "<<__FILE__<<" line "<<__LINE__<<" : "<<LOGDATA<<std::endl;
// 使用
LOG(INFO, "hello world!");
LOG(DEBUG, "i am world");
// 输出
[info Aug 13 2024 14:39:02] main.cpp line 38 : hello world!
[debug Aug 13 2024 14:39:02] main.cpp line 39 : i am world