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