首页 /  技术专区  /  C 宽屏模式 >

无宏体宏和有宏体宏

1. 宏定义

1.1 宏定义的用途

(1)减少重复劳动

比如,程序中有很多地方都要用到100这个数,当需要将100修改为200时,就需要到每个引用100的地方将100改为200,

这就是重复劳动。此时最好就将100定义为宏,

#define NUM 100,

如果需要将100修改为200时,只要修改NUM即可,在预处理时NUM自动就会被替换为200。

(2)方便阅读

我们在定义每个宏时,宏名都是有一定含义的,而不是随便定义的,有意义的宏名能够帮助我们阅读和理解代码,

比如:

#define PI 3.14

一看这个宏名你就知道这是圆周率,如果在程序中直接写3.14的话,很难理解这个数字的含义。

(3)简化复杂表达式

简化使用调用。

#define AREA(a,b) (((a)+(b))*((a)-(b)))/a*b

在代码中直接调用AREA带参宏,显然简化了代码,向这类的简化复杂表达式的宏,在复杂C源码中非常多,由于这类宏的宏体复杂,往往也成为了很多同学阅读复杂C源码的障碍之一。

(4)与条件编译配合使用

#define ARM
#ifdef ARM 
...
#endif

1.2 宏的种类

宏有两种,一种是有宏体宏,另一种是无宏体宏。

 · 无宏体宏:

#define X86

 · 有宏体宏:

#define NUM 100,#define AREA(r) (r)*(r)*3.14

1.2.1 无宏体宏

(1)什么是无宏体宏

        只有宏名、没有宏体。

(2)定义形式

        #define 宏名  

(3)举例

        #define X86

        预编译完后,由于这个宏没有宏体,所以宏直接替换为空,空就是啥也没有。

(4)这种宏有作用吗

        有作用,后面讲到条件编译时再介绍。

1.2.2 有宏体宏

有宏体宏分为两种,一种是无参宏,另一种是有参宏。

无参宏:没有参数,

#define NUM 100

有参宏:有参数,

#define AREA(r) (r)*(r)*3.14

(1)无参宏

1)定义形式

#define 宏名 宏体

3)举例

 #define     YES     1
 #define     NO      0
 #define     PI      3.1415926
 #define     OUT     printf(“Hello,World”);

预处理后,宏名会被替换为宏体。

(2)带参宏

1)定义形式

#define 宏名(参数表) 宏体

2)举例

#define  S(a,b)  a*b*10
int main(void)
{
    int va;
    va = S(3,2); //3对应a,2对应b
    printf("va = %d\n", va);
    return 0;
}

预编译处理时,将宏体中的a和b,使用参数中的3和2来替换。

va = S(a, b) ——————> va = 3*2*10

3)带参宏需要注意之处

        1)宏名和参数列表之间不能有空格

#define  S (a,b)  a*b*10

由于S和(a, b)之间有空格,宏名变为了S,宏体变为了(a,b) a*b*10,含义发生了变化。

        2)写带参宏的时,不要吝啬括号

#define  S(a,b)  a*b*10

其实这个带参宏是有缺点的,如果参数写成如下形式的话,替换后结果可能就完全背离了你的本意。

        a    b         

 S(x+1, y+2) ——————————————> x+1*y+2*10

对于预编译器来说,它再处理宏定义时,它并不知道你的使用意图是什么,它只会忠实的进行替换工作,但是替换之后是否能够达到你要的效果,这个就不一定了。

怎么解决?

为了避免这种情况,大家在定义带参宏时不要吝啬括号。

#define  S(a,b)  ((a)*(b)*10)   //为了保险起见,对整个宏体也要加()。

S(x+1, y+2) ——————————————> ((x+1)*(y+2)*10)


疑问:为什么要对整个宏体加括号?

#define ETH_MMC_BASE          (ETH_BASE + 0x0100)
#define ETH_PTP_BASE          (ETH_BASE + 0x0700)
#define ETH_DMA_BASE          (ETH_BASE + 0x1000)

*ETH_MMC_BASE = 0x67667732;

如果不加括号的话,*ETH_MMC_BASE = 0x67667732;就变为了*ETH_BASE + 0x0100 = 0x67667732。

所以,凡事宏体是一个表达式的情况,我们都要求对整个宏体加(),以保证宏展开一定不会出错。

        3)带参宏 与 函数

        这两个玩意儿长得很像,但实际上是两个完全不同的东西。


1)例子

#include <stdio.h>
#define  S(a,b)  a*b*10

void s(int a, int b)
{
    return a*b*10;
}

int main(void)
{
    int va1, va2;
    va1 = S(3, 2); //引用带参宏
    va2 = s(3, 2); //调用函数
    printf("va1 = %d, va2 = %d\n", va1, va2);
    return 0;
}

仅仅从调用来看,这两个东西确实长得很像,如果将宏也定义为小写的话,仅看调用的话,很难看出这个到底谁是函数谁宏定义。

为了能够让大家快速的区分带参宏和函数,大家在定义宏的时候,宏名一定要大写,否则在阅读代码时,很容易与函数搞混,非常不利于代码的阅读和理解。

2)二者的区别

二者是有着本质区别的:

(a)带参宏

· 处理阶段:预编译

        宏只是一个供我们程序员识别的一个符号,一旦预编译之后带参宏就会消失了,取而代之的是宏体。

· 参数列表

        带参宏的形参是没有类型的,我们使用int 、float等类型只有一个目的,就是使用类型来开辟一个变量空间,变量空间的字节数和存储格式是由类型来决定的,所以定义变量时必须要有类型说明。

        而带参宏的参数仅仅只起到替换说明的作用,不需要开辟空间来存放实参的值,既然不需要开辟空间,那就不需要类型的说明。

(b)函数

· 处理阶段:由编译、汇编、链接阶段处理

        在“预编译阶段”是不会处理函数这个东西的,在预编译前后,函数没有任何变化。

        - 编译:将c形式的函数编译为汇编形式的函数。

        - 汇编:将汇编形式的函数,转为二进制指令的函数

        - 链接:进行符号统一和重定位

· 函数是一个独立体,有调用的过程

        运行函数时涉及调用的过程

        - 调用时:从当前函数跳转到被调用的函数,开辟形参和自动局部变量时,涉及压栈操作。

        - 调用结束:返回到调用函数,释放函数的形参和自动局部变量的空间时,涉及弹栈操作

· 函数的参数列表

        函数的形参是需要被开辟空间的,所以必须要要有类型说明。




头像
0/200
图片验证码