2. 宏定义

宏(Macro)是在预处理阶段,将一个字符串和一个标识符相关联,用于在预处理阶段进行字符串替换。

宏大致可以分为以下几种:

  1. 不带参数的宏
  2. 带有参数的宏
  3. 编译器内预定义的宏

不带参数的宏定义的语法

#define 宏名  宏的内容

带有参数的宏定义的语法

#define 宏名(参数名1, 参数名2, ...)  宏的内容

说明:

  1. 宏名必须是一个标识符,一般为了和变量名做区分,一般宏名都大写。
  2. #define宏名 以及 宏的内容 之间至少有一个空格或制表符分隔。
  3. 宏的内容部分必须写在一行内,如果过长需要换行书写,则要在需要换行的行尾最后一个字符添加折行符(\)。
  4. 宏的内容部分如果不需要可以为空。
  5. 带有参数的宏的参数部分需要给出参数名,但不需要给出参数类型。
  6. 在带有参数的宏的内容中,如果有参数名出现,则用实际传入参数代替内容部分的参数名。
  7. 如果两次或以上使用 #define 指令定义同一个 宏名 则宏的定义方式必须完全相同,否则报错。

示例

  1. 使用不带参数的宏 PI 代替圆周率的浮点数字面值 3.1415926
  2. 使用带有参数的宏 AREA(rr) 替代求圆的面积的表达式 3.14*rr*rr。其中 rr 是参数,在替换处会使用 参数字符串 替换。
// filename: circle.c
#define PI         3.1415926

#define AREA(rr)   3.14*rr*rr

int main(int argc, char * argv[]) {
    double r = 10;  //圆的半径
    double length = PI * r * 2;  // 计算圆的周长。
    double area1 = PI * r * r;  // 计算面积
    double area2 = AREA(r);  // 计算面积
    printf("圆的半径: %f\n", r);
    printf("圆的周长: %f\n", length);
    printf("圆的面积1: %f\n", area1);
    printf("圆的面积2: %f\n", area1);

    return 0;
}

使用 gcc -E circle.c 命令预处理后的结果如下:

weimingze@mzstudio:~$ gcc -E circle.c
# 0 "temp.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 0 "<command-line>" 2
# 1 "temp.c"




int main(int argc, char * argv[]) {
    double r = 10;
    double length = 3.1415926 * r * 2;
    double area1 = 3.1415926 * r * r;
    double area2 = 3.14*r*r;
    printf("圆的半径: %f\n", r);
    printf("圆的周长: %f\n", length);
    printf("圆的面积1: %f\n", area1);
    printf("圆的面积2: %f\n", area1);

    return 0;
}

可见在实际使用时 PI 会替换成 3.1415926AREA(r) 被替换成了 3.14*r*r

需要注意的问题:

宏的替换只是源码级别的替换,不会做类型的判断。如我们在使用宏时写成AREA(r+1) 则会被替换成为 3.14*r+1*r+1,显然这不是我们想要的结果,因为乘法的运算符的优先级大于加法的优先级。

为了解决上述问题,我们可以尝试将宏定义 AREA(r)宏的内容部分将参数加上括号,如: #define AREA(rr) 3.14*(rr)*(rr),这样AREA(r+1) 将会替换成 3.14*(r+1)*(r+1),上述问的得以解决。

上述带有参数的宏中,如果再使用宏 AREA 是写成 AREA(++i),则会被替换成为 3.14*(++i)*(++i),显然使用者的原意是执行一次 ++i, 但替换后就会执行两次 ++i且结果也不正确,因此在使用宏的时候尽可能避免上述问题。

宏的优缺点

优点

  1. 使用带有参数的宏定义可以象函数一样使用,但没有函数调用的开销,执行效率高
  2. 与类型无关,可以编写出于类型无关的通用代码。

缺点

  1. 预处理阶段展开,难以调试。
  2. 容易出错,带有参数的宏的参数可能会被多次求值。
  3. 没有类型检查,在编辑阶段报错而非预处理阶段报错。

练习

编写带有参数的宏 MY_MAX(a,b) 返回参数 ab 的最大值。

如:

printf("%d\n", MY_MAX(100, 200));  // 打印 200
printf("%d\n", MY_MAX(99, 88));  // 打印 99