文章时效性提示
本文发布于 502 天前,部分信息可能已经改变,请注意甄别。
预处理
预定义符号
1 | __FILE__ //进行编译的源文件 |
这些预定义符号都是语言内置的,例如:
1 |
|
打印的结果是:
1 | file: /Users/miles/Library/Mobile Documents/com~apple~CloudDocs/C/预处理2024_10_29_12_04/预处理2024_10_29_12_04/main.c |
#define
#define定义标识符
语法:#define name stuff
使用#define大体上就是完成替换的操作。例如#define reg register之后在程序中写rge int a;和register int a;的效果是一致的。
1 |
|
打印的结果是
1 | 100 |
使用#define还可以替换函数,例如:
1 |
|
上面的代码就相当于在主函数中运行for(;;);,即程序死循环。
不要在#define后面加分号;,因为替换后会变成两个分号。
#define定义宏
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)
语法:#define name(parament-1ist) stuff
parament-list是一个由逗号隔开的符号表,可能出现在stuff中
注意⚠️:parament-list的左括号必须紧邻name。
1 |
|
打印的结果是25。
上面的代码存在一些问题,例如:
1 |
|
打印的结果不是期望的36,而是11,这是运算优先级的问题导致的,即计算顺序是:5+1*5+1=11
要解决这个问题,可以把宏替换进去的内容加上括号,即(5+1)*(5+1)=36。
下面是更正后的代码:
1 |
|
上面的代码也存在一些问题,例如:
1 |
|
预期的结果是100,然而打印的结果是55,这也是由于运算优先级的问题,程序的计算方法是:10 * (5) + (5) = 55。
为了解决这个问题,我们在X的最外层加上括号,即10 * ( (5) + (5) ) = 100。
更正后的代码:
1 |
|
也就是说,有关数值表达式求值的宏定义都应该加上括号,避免出现求值错误
#define 替换规则
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意: - 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
- 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
#和##
1 |
|
打印的结果是:
1 | the value of a is 10 |
##可以把位于它两边的符号合成一个符号。
1 |
|
打印的结果是1234
注意⚠️:这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。
带有副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
例如:
1 | x+1;//没有副作用 |
使用带副作用的宏参数时一定要小心,建议不要写带副作用的宏参数
1 |
|
打印的结果是12 11 13
宏和函数对比
宏经常用于简单的运算,例如比较两个数的大小。define MAX(X,Y) ( (X) > (Y) ? (X) : (Y) )
在执行小型运算过程中,宏比函数在程序的规模和速度方面更好,此外,宏是类型无关的。
命名习惯:宏名大写,函数名不要全部大写
#undef:移除宏定义
1 |
|
如果现存的一个名字需要被重新定义,使用#undef来移除宏定义
条件编译
使用条件编译指令使一条或者一段语句在程序编译时放弃编译。
1 |
|
#if后面的表达式如果为真,后面的表达式参与编译,为假,不参与编译。
多分支的条件编译
语法:
1 |
|
举个例子:
1 |
|
上例中,如果if后面的语句为真,打印1,
if后面为假且elif后面为真,打印2,
if和elif后面都为假,打印3。
判断是否被定义
1 | //定义了执行 |
通过判断symbol是否被定义来决定是否执行语句。
1 |
|
上例,如果定义了debug,则打印debug。
嵌套指令
上述的条件编译指令可以嵌套使用。