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