C语言教程-17-预处理器
预备知识:
- 头文件
C程序在编译前,要进行一个 预处理
操作,这个操作主要的工作是根据 预处理指令
对C源代码中的一些文本进行转换操作,生成另外的特定文本。
每一条预处理指令都由一个 #
开头,后接具体的指令内容。我们会介绍C语言中的各种 预处理指令
,他们对于C程序至关重要,例如包含文件、定义常量、选择编译等等。
当然,在此之前,我们其实已经见过了不少的宏定义指令了,但是仅仅一带而过,并未详细展开,在这里会进行详细讲解。
不过,对于初学者不常用的指令在此从略。
包含其他文件:#include
我们已经无数次地看到 #include
指令了,这条预处理指令用于在当前文件中包含其他文件,被包含的文件内容会被直接以纯文本的形式替换该指令。
基本语法
#include <文件名>
#include "文件名"
使用 <>
时,从标准包含目录搜索指定的文件进行包含;
使用 ""
时,优先从当前文件所处目录进行搜索,未找到时再去标准包含目录搜索。
注意,以上两种操作实际上均由实现定义。
替换文本宏:#define和#undef
基本语法:
#define 标识符 替换列表
#define 标识符(形参) 替换列表
#define 标识符(形参...) 替换列表
(C99起)#define 标识符(...) 替换列表
(C99起)#undef 标识符
注:考虑到本文面向初学者,上述语法中C99新增的2种在此并不展开讲解,读者可以自行查阅相关资料。
详细解释:
#define
指令定义 标识符 为 宏
,即后面所有出现的该 标识符 均被替换为 形参列表,并且这个替换是 纯文本替换
,并不涉及任何的算术运算。
注:标识符被定义为宏常称为“宏定义”,常称这种替换为 “宏替换”。
例如:
1 |
|
运行结果为:
1 | i=2333 |
这里将 #define
指令后面所有的标识符 A_NUM 全部简单地替换为 形参列表 表示的文本“2333”,这样,源代码中对变量 i
的赋值就变成了 int i = 2333;
另一方面,需要注意语法中特别强调了 标识符 ,这意味着其必须符合前面所述的 标识符命名规则
,否则程序报错;此外,这也意味着,如果在字符串中包含了该 标识符 文本,他会被视为字符串的一部分,并不会替换。
例如:
1 |
|
运行结果为:
1 | str=HELLO |
显然,此时并没有发生替换,也就是说 "HELLO"
被视为一个普通的字符串,不会进行宏替换。
以上这种简单替换标识符的宏被称为 仿对象宏
。
宏定义也可以类似函数那样使用形参,唯一的区别是,宏定义的形参没有类型,它依然是一个简单的文本替换。
例如,我们尝试使用宏来实现一个简单的求2者最大值的函数:
1 |
|
运行结果为:
1 | max_num = 4 |
而且由于形参x和y并没有类型限制,所以可以传入任何可以比大小的2个值:
1 |
|
运行结果为:
1 | max_num = 12.50 |
像这样定义的函数称为 仿函数宏
,当然,许多人将其称为 宏函数
,并无伤大雅。
而对于 `#undef` 指令,用于解除前面的 `#define` 指令所定义的宏,若标识符无与之关联的宏,则忽略此指令。 例如下面的代码,在定义变量 j 的时候便会报错表示宏 `A_NUM` 未定义:
1 |
|
特殊操作:#运算符与##运算符
宏替换是纯文本,自然让我们想到一些字符串操作,仿函数宏中的 #
和 ##
运算符实现了这一点。
#
运算符将标识符 字符串化,即将替换列表中的标识符加上双引号,如果标识符的值包含有"
,则会自动进行转义;
此外,该运算符还会将所有的前导、尾随空格删除,并且将任何文本中间的空白字符序列(但非内嵌字符串字面量中的)缩减为一个空格。
例如:
1 |
|
- 在任意两个标识符中间的
##
运算符将两者进行替换,然后将结果连接,若连接的结果不是合法记号,则行为未定义。
例如:
1 |
|
注意事项:
- 不要在宏中引发副作用,例如
#define SQUARE(x) ((x)=(x)*(x))
并不合适。 - 宏过长的话请在行末使用
\
进行换行,以保证代码的整洁,注意最后一行不要加\
。例如:
1 |
- 宏是可以嵌套的,对于嵌套宏,从外向内逐层展开。
- 宏仅仅是在编译期进行简单的文本替换,而不会做任何的计算,所以请不要认为仿函数宏中传递的参数是先被计算求值然后再展开!展开后的结果仍然保留原来的表达式。例如:
1 |
|
- 针对上一条指出的问题,在仿函数宏中,请对整个替换列表中出现的标识符加括号,例如:
1 |
|