概念
- C语言中,可以用 #define 定义一个标识符来表示一个常量。
定义的标识符不占内存,只是一个临时的符号,预编译后这个符号就不存在了。
预编译又叫预处理。预编译不是编译,而是编译前的处理。这个操作是在正式编译之前由系统自动完成的。
凡是以“#”开头的均为预处理指令,#define也不例外,此外还有 #include等。
- 宏所表示的常量可以是数字、字符、字符串、表达式。
- 宏名表示的是一个常量,不能给常量赋值。
- 为了将标识符与变量名区别开来,习惯上标识符全部用大写字母表示。
- 重复定义宏只有最后一个生效
如果两个宏定义之间,仅有空格和注释不同的话,两个宏定义还是同一个宏定义。
类型
- Object-like宏
如写法中的一般写法 - Function-like宏
如写法中的函数宏
写法
- 一般写法
#define 标识符 常量
#define又称宏定义,标识符为所定义的宏名,简称宏。
注意,最后没有分号,预处理指令不是语句,所以后面不能加分号。
宏定义 #define 一般都写在函数外面,与 #include 写在一起。写在函数里面也没有语法错误,但通常不那么写。
- 终止作用域
#undef 标识符
#define 的作用域为自 #define 那一行起到源程序结束。如果要终止其作用域可以使用 #undef 命令,undef 后面的标识符表示你所要终止的宏,如果在#undef之后再使用对应的标识符就会报错,没有定义对应的标识符。
- 多行宏
#define NUMBERS 1, \
2, \
3 - 多次宏替换
#define TABLESIZE BUFSIZE
#define BUFSIZE 1024
如果宏定义的代码段依然是宏的话,预处理器会继续进行宏替换的操作。TABLESIZE的最终的值就是1024。
- 函数宏
#define lang_init() c_init() //普通
#define min(X, Y) ((X) < (Y) ? (X) : (Y)) //带参数
#define str(expr) printf("%s\r\n", #expr) //字符串化
字符串化指的是,可以在宏的参数前面加入#,使入参变成字符串。
#define myprintf(...) fprintf (stderr, __VA_ARGS__) //可变参数
这种形式的宏,会把…的代表的参数扩展到后面的__VA_ARGS__中。
#define myprintf(args...) fprintf (stderr, args) //可变参数
-
用两个#将两个符号连接成一个符号
#define A(NAME) A##NAME -
预定义宏
printf("DATA:%s\r\n",__DATE__); //打印日期
预定义宏有标准预定义宏/GNU C编译器扩展实现 / 不同系统特定的预定义宏, 用双下划线开头和结尾,例如__FILE__和__LINE__
好处
- 使用宏定义可以用宏代替一个在程序中经常使用的常量,方便修改。
- 当常量比较长时,使用宏就可以用较短的有意义的标识符来代替它。
注意
可以将任意的有效的标识符定义为宏,C语言的关键字也可以。
但是在C语言中"defined"不可以作为宏的名称。
在C++中以下的关键字也不可以作为宏的名称:and,and_eq,bitand,bitor,compl,not,not_eq,or,or_eq,xor,xor_eq,因为在C++ 中系统将这些关键字预定义成了操作符。
在C++ 中,你可以使用命名操作符来代替这些符号,比如int c = a bitor b;等同int c = a | b;
预处理
其实预编译所执行的操作就是简单的“文本”替换。对宏定义而言,预编译的时候会将程序中所有出现“标识符”的地方全部用这个“常量”替换,称为“宏替换”或“宏展开”。替换完了之后再进行正式的编译。
所以说当单击“编译”的时候实际上是执行了两个操作,即先预编译,然后才正式编译。
#include<stdio.h>也是这样的,即在预处理的时候先单纯地用头文件stdio.h中所有的“文本”内容替换程序中#include<stdio.h>这一行,然后再进行正式编译。
用宏来实现日志功能
#define LOG(str) \
do \
{\
fprintf(stderr, "[%s:%d %s %s]:%s\r\n", __FILE__, __LINE__, __DATE__, __TIME__, str); \
}while(0)
int main()
{
LOG("malloc for buf");
return 0;
}
看似这个do() while(0)没有什么意义。但是这是一个编写宏内多行代码段的好习惯。使用do{}while(0)包含的话,可以作为一个独立的block,进行变量定义等一些复杂的操作,该用法主要是防止在使用宏的过程中出现错误。具体看参考对应的链接。
连接符号的用法
#include <stdio.h>
#define A1 printf("print A1\r\n")
#define A2 printf("print A2\r\n")
#define A(NAME) A##NAME
int main()
{
A(1); //打印print A1
return 0;
}
如以上例子,输入1,通过##连接符号就变成了A1,而A1刚好有一个函数宏A1,所以最终就调用了A1这个函数宏。
这种写法可以通过根据不同的传参调用不同的函数宏。
宏在系统中的使用样例
比如offsetof,container_of等,具体看参考里的链接。
参考
C语言宏的用法详解
https://blog.csdn.net/armlinuxww/article/details/93965786
#define用法,C语言#define详解
http://m.biancheng.net/view/187.html