⚽3.预处理详解
C语言允许在源程序中加入一些“预处理指令”(preprocessing directive), 以改进程序设计环境,提高编程效率。这些预处理指令是由C标准建议的, 但它不是C语言本身的组成部分,
不能用C编译系统直接对它们进行编译(因为编译程序不能识别它们)。必须在对程序进行正式编译(包括词法和语法分析、代码生成、优化等)之前,
先对程序中这些特殊的指令进行“预处理”(preprocess, 也称“编译预处理”或“预编译”)。把预处理指令转换成相应的程序段,
它们和程序中的其他部分组成真正的C语言程序, 对预处理指令进行的预处理工作,
是由称为C预处理器(preprocessor)的程序负责处理的。
在预处理阶段,预处理器把程序中的注释全部删除; 对预处理指令进行处理, 如把#include指令指定的头文件(如stdio.h)的内容复制到#include指令处; 对#define指令,进行指定的字符替换(如将程序中的符号常量用指定的字符串代替), 同时删去预处理指令。
预定义符号
这些预定义符号都是语言内置的。
举个栗子:
实际使用场景举例:创建log日志
⚾4.#define
①#define 定义的标识符
语法:
举个栗子:
②#define定义宏
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。
下面是宏的申明方式:
#define name( parament-list ) stuff
其中的parament-list
是一个由逗号隔开的符号表,它们可能出现在stuff中。
注意: 参数列表的左括号必须与name紧邻
。 如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
如:
这个宏接收一个参数x . 如果在上述声明之后,你把
置于程序中,预处理器就会用下面这个表达式替换上面的表达式:
警告: 这个宏存在一个问题: 观察下面的代码段:
乍一看,你可能觉得这段代码将打印36这个值。 事实上,它将打印11. 为什么?
替换文本时,参数x被替换成a + 1,所以这条语句实际上变成了: printf (“%d\n”,a + 1 * a + 1 );
这样就比较清晰了,由替换产生的表达式并没有按照预想的次序进行求值。
在宏定义上加上两个括号,这个问题便轻松的解决了:
这样预处理之后就产生了预期的效
这里还有一个宏定义:
定义中我们使用了括号,想避免之前的问题,但是这个宏可能会出现新的错误。
这将打印什么值呢?
**warning:**看上去,好像打印100,但事实上打印的是55. 我们发现替换之后:
乘法运算先于宏定义的加法,所以出现了55
这个问题,的解决办法是在宏定义表达式两边加上一对括号就可以了。
提示:
所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或 邻近操作符之间不可预料的相互作用。
#define替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
-
在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
-
替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
-
最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
-
宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
-
当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
✊5.#和##作用
1. #的作用
使用 #
,把一个宏参数变成对应的字符串
如何把参数插入到字符串中 ?
举个栗子:
2. ##的作用
##
可以把位于它两边的符号合成一个符号
。 它允许宏定义从分离的文本片段创建标识符。
举个栗子:
注: 这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。
⏲6.带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
什么是副作用:
比如
X+1 -->没有副作用
++X -->有副作用
MAX宏可以证明具有副作用的参数所引起的问题。