聊聊C语言中的宏

1 简介

宏在C语言中是一段有名称的代码片段。无论何时使用到这个宏的时候,宏的内容都会被这段代码替换掉。

在C语言中使用#define来定义宏,你可以将任意的有效的标识符定义为宏,但是defined关键字不可以作为宏的名称。在C++中以下的关键字也不可以作为宏的名称:and,and_eq,bitand,bitor,compl,not,not_eq,or,or_eq,xor,xor_eq。

2 两种宏的类型

主要有两种宏,一种是在使用时类似于数据对象称为Object-like宏,另一种在使用时类似于函数调用称为Function-like宏

2.1 Object-like宏

Object-like宏,可以比较简单的进行代码段的替换。这种方式最常用做表示常量数字。例如:


使用该宏的时候就可以用来替换数字。


预处理器将会把该宏替换为对应的数字,如下所示。


按照惯例,宏一般都写作大写字母。

多行的宏

宏结束于#define的行尾,如果有必要的话,可以在末尾添加反斜杠来将宏定义成多行。


多次宏替换
如果宏定义的代码段依然是宏的话,预处理器会继续进行宏替换的操作。


2.2 Function-like宏

宏还可以被定义成下面的形式,使用该宏的时候,类似于调用函数,这类宏的定义中,宏的名称后面紧跟一堆括 (与括 之间不能有空格)。


调用该类宏的时候,也必须跟一个括 ,如果不跟括 的话,会显示语法错误。

宏的参数

Function-like宏可以接受参数,类似于真正的函数一样。参数必须是有效的C语言标识符,使用逗 隔开


字符串化

字符串化指的是,可以在宏的参数前面加入#,使入参变成字符串。例如:


连接符

在宏中,可以使用两个#将两个符 连接成一个符 。


在该例子中,调用宏A(1)时,NAME为1。A##NAME这个符 连接,即将A和1连接成了一个符 A1,然后执行宏A1的内容,最终打印出来了print A1。

可变参数

定义宏可以接受可变数量的参数,类似于定义函数一样。如下就是一个例子


这种形式的宏,会把…代表的参数扩展到后面的__VA_ARGS__中。在该例子中,就会扩展为fprintf(stderr, “1234rn”)。
如果你的参数比较复杂,上面的myprintf还可以定义为如下的形式,用自定义的名称args来表示参数的含义:


3 预定义宏

3.1 标准预定义宏
标准的预定义宏都是用双下划线开头和结尾,例如__FILE__和__LINE__,表示文件的名称和该行代码的行 。


3.2 常见的GNU C编译器扩展的预定义宏


3.3 系统特定的预定义宏

系统特定的预定义宏,在不同的操作系统和CPU上面,呈现的结果可能会有所不同。例如我的环境是Linux X86_64平台。执行下面的代码


4 C++的命名操作符

前面说过C++ 中有and,and_eq,bitand,bitor,compl,not,not_eq,or,or_eq,xor,xor_eq这些命名不可以用作宏的名称。那么为什么呢?是因为在C++ 中系统将这些关键字预定义成了操作符。


所以在C++ 中,你可以使用命名操作符来代替这些符 。例如:


5 宏的取消

使用#undef可以将已经定义的宏取消掉


如果在#undef之后再使用BUFSIZE就会 错,没有定义BUFSIZE

6 宏的几种常见使用场景

6.1 替代魔法数字

这个是C语言中非常常见的一种用法,增加代码可读性。


6.2 LOG日志


这里定义了LOG宏,可以打印日志,输出当前的代码文件和行数,以及时间和用户定义的内容。自行扩展可以增加更丰富的内容。

注意:这里使用了一个do{} while(0)来包含宏的内容。看似这个do() while(0)没有什么意义,但是这是一个编写宏内多行代码段的好习惯。使用do{}while(0)包含的话,可以作为一个独立的block,进行变量定义等一些复杂的操作,该用法主要是防止在使用宏的过程中出现错误。例如


在这种情况下,if后面没有跟大括 ,我们foo宏里面定义的是两个语句,其中fun2是在if条件判断之外的。这样就不符合我们的预期了。

如果使用大括 来避免上面的错误,还会出现下面的错误:


这里在add(x, y)之后有个分 。会造成else匹配不到if编译错误。所以为了防止发生这些错误,可以使用do{}while(0)将函数体包含。

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2022年11月3日
下一篇 2022年11月3日

相关推荐