c进阶篇(十二)——深入理解宏定义

前言:
   c的语法中,最灵活的是宏使用,本篇文章将详细探讨宏的几种用法。

1 概念

  在c/c++中,宏定义将一个标识符定义为一个字符串,源程序中的该标识符均以指定的字符串来替代。因此理解宏定义用法的核心是“替换”。宏定义是在预处理阶段处理的,也即在编译前就完成了宏的替换。宏的使用可以使得编程者编辑的代码得到简化,也可提高代码的运行效率。

2 规则

  宏定义的语法规则如下。

1
#define <宏名><(参数列表)> <宏体>

  宏可以带参数列表也可以不带,参数列表同样也是进行替换。处理定义处在代码中所有宏名字符串都会替换为宏体。

3 不带参宏定义

  此种宏用法发简单,但却大有用处。不带参宏是没有参数列表的宏定义。

3.1 空宏用作条件编译

  条件编译离不开宏,而空宏更是常用于条件编译的条件。空宏指的是没有宏体的宏定义。空宏结合带 # 的条件编译可对代码编译形成条件控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#define COND1
//#define COND2

//形式1。
#ifdef COND1
//Code
#else
//Code
#endif //COND1

//形式2。
#if defined(COND1)
//Code
#else
//Code
#endif //COND1

//多个组合判断。
#if defined(COND1) && !defined(COND2)
//Code
#elif !defined(COND1) && defined(COND2)
//Code
#else
//Code
#endif //COND1 COND2

  通过空宏作为条件编译条件,此时并不关心宏值,只关心宏是否被定义。通过宏的该用法加在头文件中,可以避免头文件被重复包含,放在源文件中将源文件中的代码屏蔽,避免不同源文件重复定义。

1
2
3
4
5
6
7
8
9
10
//*.h文件模板。
#ifndef HDR_H
#define HDR_H
//Code
#endif //HDR_H

//*.c文件模板。
#ifdef HDR_H
//Code
#endif //HDR_H

3.2 常量宏方便统一修改

  若一个参数在多处都被使用,当要修改该参数时需要在使用处一一修改,更好地办法是将该通用参数定义为宏,在预处理阶段自动替换。常量宏的常量可以是数值常量、字符串常量或表达式常量,现在的编译器优化会将常量表达式计算出结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#define PI 3.1415926f

/**
* @fn float CalcCirPerim(float fR)
* @brief 计算圆周长。
* @param[in] fR 半径。
* @return 圆周长。
*/
float CalcCirPerim(float fR)
{
return 2 * PI * fR;
}

/**
* @fn float CalcCirAr(float fR)
* @brief 计算圆面积。
* @param[in] fR 半径。
* @return 圆面积。
*/
float CalcCirAr(float fR)
{
return PI * fR * fR;
}

  以上代码多处都是用了 PI 这个数学常量,为了避免对 PI 的精度进行修改时要对所有使用处一一修改,还容易出现遗漏处,最好的办法是将这个常量定义为宏。

3.3 常量宏用作条件编译

  常量宏也常与条件编译搭配,设计出不同的代码编译逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define SW 0

#if SW == 0u
//Code
#elif SW == 1u
//Code
#elif SW == 2u
//Code
#else
//Code
#endif //SW

#define ENABLE 1u
#define DISABLE 0u

#define FUN_SW DISABLE //默认关闭功能。

//重新定义宏
#undef FUN_SW
#define FUN_SW ENABLE //打开功能。

#if FUN_SW
//Code
#endif //FUN_SW

  定义好的常量宏可以使用 #undef 重新定义其值(空宏也可重定义)。

3.4 将代码段定义为宏

  直接将固定的代码段定义为宏这种用法并不常见,因为一般复用率较高的代码段都会封装为函数或宏函数,而不会直接使用一个宏名表示代码段。在c中不像对象语言是没有异常处理机制的,我见过一种用法是使用宏来实现类似c++中的 try-catch 异常处理机制,不建议这样用,在实际可以通过其他方式实现相同的效果,或者干脆用支持 try-catch 的语言好了。

1
2
3
4
#define CODE \
{ \
/*Code*/ \
}

3 带参宏定义

  宏定义是支持带参的,但是宏的参与函数参不同,宏参也是直接替换不会进行类型检查,所以这里可能由替换引起一些“负作用”。

3.1 带返回值的宏函数

  宏是直接替换,当替换的对象是常量、表达式或函数调用时宏函数可起到带返回值的效果。

1
#define Add(Num1, Num2)     ((Num1) + (Num2))
1
#define GetPi()             3.1415926f