前言
本篇博客承上启下,在 前篇程序执行过程 的基础上,进一步介绍 宏定义的各种使用方法和基本的注意点,为后篇 宏定义的具体应用打下基础,主要涉及 宏注释、宏定义的执行顺序、宏定义的有效范围以及该如何使用宏定义和宏定义使用的基本注意点,了解宏定义的生前死后。
主要使用 Linux 平台演示,具体为 CentOS7
宏注释的执行顺序
先来讨论一个有趣的问题,有助于更加清晰的理解 C程序的编译执行过程,就是如果我们将注释符号定义为宏,会发生什么现象呢? 这个宏定义还会生效吗?注释还会生效吗?
话不多述,代码执行会告诉我结果!先看代码:
#include <stdio.h>
#define BSC //
int main()
{
BSC printf("hello world\n");
printf("you can see me!\n");
return 0;
}
如果是先进行宏替换,就不会输出 hello world 这个语句,因为这个宏BSC 正是 注释符,但是如果是先去注释,则代码中并没有注释,然后再替换宏,第5行就变成了:
// printf("hello world\n");
就变成 注释符 被留在了代码中。
直接看运行结果:
是正常输出的,我们直接查看预处理的结果:
通过预处理结果可以看出:先去注释,然后 宏替换 才是正确的执行顺序,这里是因为 宏定义本身就是注释,所以在预处理时,先去注释:就是把这个宏定义的注释去掉了,该宏定义就变成了:#define BSC
宏定义的内容是空白,所以在替换的时候,就将 BSC 替换为空白,所以预处理的结果就是正常的语句了。
在实际编码中,很不推荐使用 宏注释,太过标新立异,影响阅读
宏定义表达式
有如下代码:
#include <stdio.h>
#define SUM(x) (x)+(x)
int main()
{
printf("%d\n",SUM(10));
printf("SUM(20)\n");
return 0;
}
运行效果:
此代码在运行时,会将符合 宏 SUM(x)
的式子SUM(10)
进行替换,将x 进行带入,就变为:printf("%d\n",(10)+(10));
进而输出结果。
宏定义初始化
使用 宏定义 也可以实现 对变量的初始化,如下代码:
#include <stdio.h>
#define INIT_VAL(a,b) a=0;b=0
//这里也可以使用 续行符 进行优化显示
int main()
{
int x=10,y=20;
printf("before: %d, %d\n",x,y);
INIT_VAL(x,y);
printf("after: %d, %d\n",x,y);
return 0;
}
运行结果:
但是这样的使用方法是有问题的,如果将上面的代码进行简单的扩充修改,以下代码:
#include <stdio.h>
#define INIT_VAL(a,b) a=0;b=0
int main()
{
int x=10,y=20;
int flag=0;
scanf("%d",&flag);
if(flag)
INIT_VAL(x,y);
else
x=100;
y=200;
return 0;
}
运行结果:
原因就是在运行时:在预处理阶段,将宏定义内容进行替换,替换后问题就出现了,这里可以查看预处理后生成的 test.i 文件,就可以看到问题所在了:
要解决这个问题也很简单,只需要注意编码规范就可以了,建议使用 {}
来约束代码。
但是这样进行约束程序员代码规范的方式并不可取,这里使用的解决方案是,将宏定义变为 一条语句,使用这个语句来玩出需要的操作:
#include <stdio.h>
//#define SUM(x) (x)+(x)
#define INIT_VAL(a,b) do{a=0;b=0;}while(0)
int main()
{
int x=10,y=20;
int flag=0;
scanf("%d",&flag);
if(flag)
INIT_VAL(x,y);
else
x=100, y=200;
return 0;
}
这样不论 使用还是不使用 {}
都不会产生影响了,因为其一定会执行一次了。这样就实现了使用宏定义 来进行遍历初始化。
在使用 { }
之外,还应该养成的代码规范是将所有 宏函数 中使用的变量都使用 ()
进行约束,如下宏定义:
#define GETSUM(x,y) ((x)+(y))
全部都使用 ()
来约束,可以防止 x
或 y
或 x+y
为 表达式,导致执行时出现差错。
使用注意点
- C语言中, 宏只能拓展为用 大括号括起来的初始化、常量、小括号 括起来的表达式、类型限定符、存储类标识符 或
do-while-zero
结构,此外宏定义也不能 重定义 语言的语法,而且宏定义语句的末尾,必须省略分号 - 参数宏 的调用不能缺少参数,如果此宏 有参数的话
- 传递给 函数宏 的参数不能包含看似 预处理指令 的标记
- 在定义函数宏时,每个参数实例都应该以小括号括起来 (优先级问题) 除非他们是作为
#
或##
的操作数使用。
下面的带有空格的代码酌情使用:
#include <stdio.h>
#define INC(a) ((a)++) //定义不能带空格
int main()
{
int i = 0;
INC (i); //使用可以带空格,但是严重不推荐(不要处处显得自己不一样哦)
printf("%d\n", i);
}
宏的有效范围
宏定义 可以出现在代码中的任何位置,与是否在函数内外,都没有关系(不受函数作用域影响)宏定义后,对于宏定义 以下的所有代码都有效。
宏定义可以限制使用范围,使用:#undef
来显示使用范围,如下:
#include <stdio.h>
int main()
{
#define M 10
printf("before: %d\n",M);
//从定义宏 到 结束定义范围,宏使用有效
#undef M
printf("after: %d\n",M);
return 0;
}
使用效果:
其他注意点
- 尽量不要在代码块中使用 #define 和 #undef,虽然C 语言语法是支持的,但是这样可能会使人对其作用域产生疑惑
# undef
可以使用- 尽量使用 普通函数,不要经常使用 宏定义函数,这样可以减少代码空间的占用(ROM空间),而且也可以减少出错的可能
- 预处理指令中的所有宏表示在使用前都应该先定义
- 即使是在普通函数中定义了宏,如果未调用该函数,也是在宏定义之下,都是该宏的作用范围
最后
感谢观赏,一起提高,慢慢变强