C语言参悟-文本替换宏

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语言的,FILELINE 可以输出日志去定位到某一行代码

一个简单的例子如下:

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
相关推荐
deja vu水中芭蕾16 分钟前
嵌入式C面试
c语言·开发语言
stm 学习ing2 小时前
HDLBits训练3
c语言·经验分享·笔记·算法·fpga·eda·verilog hdl
CSND7406 小时前
Ubuntu vi(vim)编辑器配置一键补全main函数
linux·c语言·ubuntu·编辑器·vim
Crazy learner7 小时前
C 和 C++ 动态库的跨语言调用原理
c语言·c++
人才程序员13 小时前
QML z轴(z-order)前后层级
c语言·前端·c++·qt·软件工程·用户界面·界面
w(゚Д゚)w吓洗宝宝了13 小时前
C vs C++: 一场编程语言的演变与对比
c语言·开发语言·c++
爱吃西瓜的小菜鸡18 小时前
【C语言】抽空洗澡
c语言·开发语言·学习·算法
encoconut18 小时前
动态内存管理
c语言
人才程序员1 天前
【无标题】
c语言·前端·c++·qt·软件工程·qml·界面
思麟呀1 天前
在C语言基础上的C++第一章(基础的输入输出和动态内存开辟)
c语言·c++·学习