1. 宏的一些值得强调的地方
1.1 预处理完之后,宏定义和宏引用都会消失
#define NUM 100 //宏定义,预处理后消失 int main { int a; a = NUM; //宏引用,预处理后被替换为宏体,宏引用消失 return 0; }
1.2 宏名的字母一般习惯大写,以便与变量名、函数名相区别
有关宏名大写的习惯并不是没事找事,如果宏名小写的话,会很容易和正常的变量和函数混淆。
(1)小写的无参宏很容易和变量混淆
#define pi 3.14 int a = 10; int main(void) { int c = a + pi; return 0; }
如果pi不大写的话,仅看使用的位置,很难判断出pie是宏还是变量。
(2)小写的带参数宏很容易和函数混淆
#define area(a, b) ((a)*(b)) int main(void) { int c = area(10, 20); //仅看调用的位置,很难判断area是一个宏还是一个函数。 return 0; }
(3)宏名与变量和函数混淆到底有什么不好
我们在实际开发时,不管是因为调试还是代码维护的原因,我们都需要阅读别人的源码,或者别人阅读自己的源码,或者自己阅读自己的源码(程序写大了自己都糊涂了),阅读代码时我们不可能每个函数都去查看它的定义,我们需要通过名字快速识别然后了解它的作用,但是如果你把宏和变量函数都定义成小写的话,阅读宏时就很容易误解为变量和函数,但是实际上它们之间是有本质区别的,这种理解的错误就非常容易给我们代码阅读带来很多的困惑,进而影响代码的调试和维护。
正是基于这样的道理,我们这里要求大家尽量将宏写成大写的,而且一定要见名识意。
疑问:难道真的没有小写的宏吗?
其实也不是,在少数某些特殊情况下,还真有定义为小写的,但是这种情况比较少见。
比如大家在学习标准IO函数时,有三个宏(stdio.h):
stdin:标准输入(从键盘输入数据)
stdout:标准输出
stderr:标注出错输出
这三个宏其实就是小写的,之所以写成小写,应该是历史遗留问题。
除了少数的特例之外,对于宏来说,我们要都要大写。
1.3 宏定义不是C语句,不必在行末加分号
这个在前面就介绍过,不过以下例子的;算什么
#define STUDENT struct student{int a; int b;};
例子中的分号只是宏体struct student{int a; int b;};的一个组成部分而已。
1.4 所有预编译的代码都是独占一行的(不能多行)
#define STUDENT struct student{int a; int b;};
为了独占一行,我把结构体写在了一行中,但是这样子不方便理解,我们往往会把它改成如下形式
#define STUDENT struct student{\ int a; \ int b;\ };
加了\(连行符)后,其实这几行在同一行中。
1.5 宏的作用域 与 #undef
正常情况下的宏作用域为从定义为位置开始,一直到文件的末尾。如果你希望结束宏的作用域的话,可以使用#undef这个预编译关键字。
a.c
#define NUM 100 int fun(); int main(void) { int a = NUM; return 0; } #undef NUM int fun() { int a = NUM;//这里将无法使用这个宏 }
这里虽然是以.c文件来举的例子,但是如果将宏定义在.h中时,它作用域以及#undef的用法也是一样的。
对于#undef这个预编译关键字来说,在我们自己的代码中用的比较少,但是大家在阅读OS、库、框架等复杂C源码时,不时的还是会见到这个关键字。
1.6 定义宏时可以嵌套引用其它的宏,但是不能嵌套引用自己
(1)嵌套其它宏
#define WIDTH 80 #define LENGTH (WIDTH)+40 #define AREAWIDTH*(LENGTH) int main(void) { int a = AREA; return 0; }
请问:如下写法正确吗?
#define AREAWIDTH*(LENGTH) #define WIDTH 80 #define LENGTH (WIDTH)+40 int main(void) { int a = AREA; WIDTH*(LENGTH) ————>80*(LENGTH) ————>>80*40 return 0; }
这个写法是正确的,只要宏引用的位置在定义位置的作用域范围内就行。
显然AREA的引用都在AREA、WIDTH、LENGTH作用域内,所以AREA的引用在替换时,完全不存在任何问题。
int a = AREA ——> int a = WIDTH*(LENGTH) ——> int a = 80*(80+40)
所以只要宏引用的位置都在“宏定义”的作用域范围内,对于有嵌套关系的几个宏来说,它们的先后关系不存在任何影响。
(2)为什么不能嵌套自己
#define AREA AREA*10 int main(void) { int a = AREA; return 0; }
进行宏替换:
int a = AREA ————> int a = AREA * 10
从这个例子可以看出,嵌套自己时在预编译器做完替换后,最后还剩一个宏名,这个宏名无法再被替换,最后留给第二阶段编译时,将变成一个无法识别的符号,从而报错。
所以宏不能嵌套自己,这个函数不一样,函数嵌套调用自己是递归,宏嵌套引用自己就是犯错。
1.7 只作字符替换,不做正确性检查
(1)为什么不做正确性检查
预编译器处理宏时,预编译器只关心替换的事情,至于替换的宏体的写法是否正确,预编译器本身并不做检查,因为判断写法是否正确的这件事情是由第二阶段的编译来做的。
(2)例子
a.c
#define NUM 100WEE int main(void) { int a = NUM; return 0; }
例子中整形数100WEE的写法完全是错的,但是在预编译时根本不会提示任何错误,预编译器会忠实的将NUM换为100WEE,
演示:gcc -E a.c -o a.i //仅预编译
但是后续编译时就会报无法识别100WEE的错误。
演示:gcc -S a.i -o a.s