去注释
我们对文件test.i只进行预处理操作,打开test.i和test.c,我们发现原来在test.c中被注释的几行代码在预处理后的test.i中不存在了。这就是预处理操作中的去注释。
宏替换
可以发现,#define定义的标识符常量和宏都在预处理阶段被替换了。这就是宏替换
而对于这个宏替换还有一个小细节,我之前的博客写过#define的#和define之间是可以有空格的,那么这里将要说的是:我们在使用宏时宏的名字和括号也是可以隔空格的。
代码正常运行,没有任何的错误和警告,但要注意的一点是:在定义宏的时候名字和括号之间是不能有空格的,只有在使用宏的时候名字和括号之间才可以存在空格。
**注意:**关于#和define之间可以存在空格和使用宏时名字和括号之间可以存在空格,这个知识点我们只需要了解即可,当我们在编写代码的时候严格不推荐这种写法。
去注释和宏替换哪个先执行呢?
我们将注释符号//使用#define 定义成BSC,去注释和宏替换哪个先执行,无非就两种情况。
-
宏替换先执行
那么最终的结果应该是什么也不输出,因为BSC先被替换成//,然后再去注释,这一行代码就会被去掉,所以什么也不输出。
-
去注释先执行
最终的结果应该是输出you can see me,第一步先去注释,会把
#define BSC //
中的//去掉(//本身也是注释),那么这一句就会变成#define BSC
,而我们知道在#define定义标识符常量时,可以不写内容。再下一步执行宏替换时BSC会被替换成空,那么代码就会变为printf("you can see me!\n");
,该代码当然可以正常输出了。
而根据我们上图的结果,我们可以确定结论:去注释是先于宏替换的!
那么对如下代码如何理解呢?
#include <stdio.h>
#define BMC /*
#define EMC */
int main()
{
BMC printf("you can see me!\n"); EMC
return 0;
}
可以看到,test.i中经过预处理操作后内容变成了这样,这样的代码当然不会运行成功了。
test.c到test.i的转变过程(预处理):先去注释,把上图右边绿色的给删除掉,宏定义就只剩下#defien BMC
然后再进行宏替换,将BMC替换成空,那EMC呢?EMC此时就是一个未命名的符号,导致编译错误。
宏进行多语句替换
平常我们使用宏,一般都是定义标识符常量,一个标识符代表一个单词或数字,然后免不了有一种情况就是使用宏来进行多语句的替换。也就是一个宏的标识符代表多个语句。
#include <stdio.h>
#define INIT_VALUE(a, b)\
a = 0;\
b = 0;
int main()
{
int flag = 0;
scanf("%d", &flag);
int a = 100;
int b = 200;
printf("before: %d %d\n", a, b);
if(flag)
INIT_VALUE(a, b);
else
printf("error!\n");
printf("after: %d %d\n", a, b);
return 0;
}
我们知道在使用if、else时如果不加{}
,则默认if和else之后各自只能写“一条语句”,而看上图,我们很明显符合这一规律啊,我们来预处理一下看看什么情况。
仔细查看,发现宏替换后,if后面跟了三条语句,从而导致else无法与if构成系统,导致出错。
解决这一问题很简单,只要我们有一个良好的书写风格,也就是无论if、else后跟几条语句,我们统统都加上{}
,也就不会导致这样的错误。但是我们要清楚,一个良好的代码,是需要能够具有普适性的,也就是在面对恶劣的环境下也尽量能够运行成功,所以我们这里的修改方案不能是增加if、else后面的括号,而是要对宏进行修改。
来看下面的代码
#include <stdio.h>
#define INIT_VALUE(a, b)\
do{\
a = 0;\
b = 0;\
}while(0)
int main()
{
int flag = 0;
scanf("%d", &flag);
int a = 100;
int b = 200;
printf("before: %d %d\n", a, b);
if(flag)
INIT_VALUE(a, b);
else
printf("error!\n");
printf("after: %d %d\n", a, b);
return 0;
}
为什么使用的是do-while-zero结构呢?因为当if为真时,我们必须要执行INIT_VALUE,而我们只执行一次,do-while-zero的结构完美的解决了必须要执行并且只执行一次的条件。