2021.11.11 10:38:56
预处理器定义了一些符号,它们的值是一些常量。
|:—:|:—:| |符号|含义| |_FILE_
|进行编译的源文件名|
|_LINE_
|文件当前行的行号|
|_DATE_
|文件被编译的日期(年月日)|
|_TIME_
|文件被编译的时间(时分秒)|
|_STDC_
|如果编译器支持ANSI C,它就是1,否则未定义|
首先,这是它的正式定义:
#define name stuff
这样,每当有name
出现在这一行的后面时,预处理器就会把它替换成stuff
。
#define
提供了一个机制:可以将参数替换到文本中去。这种实现被称作宏。下面是它的声明方式:
#define name(parameter-list) stuff
parameter-list,也就是参数列表,是一个由逗号分隔,每一项都可能出现在stuff中的列表。此处的括号必须与name紧邻。下面是一个实例:
#define SQUARE(x) x * x
如果把SQUARE(5)
放在随后的代码中,预处理器就会把它替换成5 * 5
。但是,如果是SQUARE(5+1)
呢?很显然是5+1 * 5+1
,不是我们预期的结果。要修复这个问题,就把宏改成这样:
#define SQUARE(x) (x)*(x)
那如果宏定义中间的符号是加号而非乘号呢?结果也非预期。我们又要改一改了:
#define ADD(x) ((x)+(x))
这样就安全了吗?并不。如果x
是某种值会改变的表达式(例如x=getchar()
,或者设想你写了一个用来比较大小的宏MAX(a,b)
,然后如此调用:MAX(b++,c++)
),那么替换后,两个x
的值也不会相等。也就是说,作为宏参数的表达式会被多次求值。
那么为什么要使用宏呢?有三点原因:宏可以做到函数做不到的事;并且,宏的执行效率要高于函数。
宏比函数的效率高:因为调用函数时,需要为函数分配栈空间等,所以多少会有时间损耗。在某些场景下更是明显。宏唯一的开销是编译时的时间变长,程序体积变大。
宏能做到更多:例如这个:
#define MALLOC(n, type) \
((type*)malloc((n)*sizeof(type)))
类型是无法作为函数参数进行传递的。
#define MAX(a,b) ((a)>(b)?(a):(b))
它与类型无关。如果用函数来实现,那么就需要很多不同版本的函数了。
这里注意,宏名一般用大写字母表示。这是约定,为了区分它和函数而设定。因为它和真正的函数还是有着不同之处的。
注意
这里有两点注意事项:一个是,可以用反斜杠
\
来让宏换行书写而不间断;另一个是,注意宏定义末尾并没有加分号,这是因为我们希望在书写时,可以像调用函数一样调用它,而不会因为没注意到重复分号,而在一些场合(如if-else
)中将两条语句错当成一条,从而造成错误。
用于移除一个现存的宏定义:
#undef name
编译时,可以在编译选项中定义宏。
#include <stdio.h>
int main(void)
{
int array[ARRAY_SIZE];
...
源码中并没有给出ARRAY_SIZE
的定义,所以我们必须在编译时指定。
通用格式为:
-Dname
-Dname=stuff
所以我们应该这样给出它的定义:
gcc main.c -DARRAY_SIZE=100
#if constant-expression
statements/* #elif可选 */
#elif constant-expression
statements#else
statements#endif
constant-expression
,即常量表达式,意思是说要么它是一个字面值常量(比如1),要么就是用define定义的符号。
此时,预处理器就会根据这几个常量表达式来对源代码选择性地编译了。在进行debug时尤为有用。定义宏DEBUG
,若值为1则编译一些测试时才会用的语句;否则只编译其他语句。
同时它还有个较常用的指令:是否被定义
//这几条都是等价的
#if defined(symbol)
#ifdef symbol
//还可以用逻辑运算
#if !defined(symbol)
//和下面这条等价
#ifndef symbol
上面所说的那些条件编译指令也支持嵌套。
#include
表示将后面跟随的文件的所有内容复制并替换这一行语句。它有两种形式:
//这表示函数库文件
#include <stdio.h>
//这表示同目录文件。如果找不到,则会在函数库目录查找
包含#error
,#line
,#progma
等。不一一介绍了。
宏的内容基本就是这些了。