首页 /  技术专区  /  C 屏幕太窄?试试伸展一下吧 >

宏定义的一些值得注意的地方

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 




0/200
图片验证码