加载中...
从零接触C语言(初览)-X: 宏

从零接触C语言(初览)-X: 宏

周二 9月 02 2025
1780 字 · 8 分钟

宏是一种十分通用的思想, 核心思想是预录制复杂操作, 然后实现 用简单的指令自动执行复杂操作

如果你十分喜欢玩游戏, 你可能听说过一种宏: 鼠标宏 或 键盘宏

鼠标宏或键盘宏 可以实现对鼠标、按键一系列操作的预录制, 然后将预录制完成的操作绑定到目标按键上, 之后只需要按下一个按键就能够按照预录制好的操作, 完整执行

如果你能够熟练使用Excel, 你或许使用过其中的录制宏的功能

Excel提供有录制宏的功能, 可以将表格操作(数据格式化、复制粘贴、数据计算等操作)进行录制, 录制完成之后可以自动转换为VBA宏, 在之后 可以直接使用宏完成相同的数据操作

这就是宏的思想

C语言, 按照宏的思想, 实现了一种语法简单的宏定义功能

#define

#define就是C语言标准提供的宏定义语法

C语言标准提供的宏定义语法非常的简单:

C
#define 标识符 替换内容

只需要简单拿的一句话, 就能完成一个宏的定义

不过, C语言的宏有什么意义? 又如何使用呢?

通过一段简单的代码来理解:

C
#include <stdio.h>

#define DOUBLE_FLOAT    double
#define PI              3.1415926

int main() {
    DOUBLE_FLOAT pi = PI;
    printf("pi: %lf\n", pi);

    return 0;
}

可以看到, main()函数中并没有直接使用double进行变量的定义与初始化

但, 确实完成了变量pi的定义与初始化, 因为printf()函数可以访问pi

这段代码中, 我们通过#define定义了两个宏: DOUBLE_FLOATPI

我们尝试使用DOUBLE_FLOAT定义变量, 并尝试给变量赋值为PI

结果表明, 这个尝试成功了

这就是C语言中宏最简单的使用: 可以使用宏, 对目标数据、关键字等 “重新起一个标识符名字”

带参数的宏

#define定义宏, 最简单的用法就是:

C
#define 标识符 替换内容

但, #define还可以定义带参数的宏:

C
#define 标识符(参数列表) 替换内容

定义带参数的宏时, 替换内容部分有些不同: 此时, 替换内容中, 只能包含参数列表中的参数(可以不包含参数)

还是通过一段简单的代码理解:

C
#include <stdio.h>

#define MULTI(x, y) ((x) * (y))

int main() {
    printf("MULTI result: %d\n", MULTI(2, 2));

    return 0;
}

image-20250903153214439

这段代码, 我们定义了一个带参数的宏MULTI(x, y), 尝试去实现x * y的操作

不过代码中好像多做了一些看起来没有用的操作: ((x) * (y)), xy被括号括起来了

不过, 尝试成功了


这两次#define的使用, 或许你会发现:

第一次使用, #definetypedef好像

因为, 看来去#define可以给数据类型起一个另外的名字使用, 而typedef实际就是做这个的

第二次使用, #define和函数好像

因为, 函数也可以通过类似标识符(参数)的方式, 执行目标操作

但, 事实上 它们是完全不同的

究竟怎么个不同, 只要了解了#define的本质, 就可以理解了

#define的本质 **

C语言中, #define的本质其实非常的简单: 文本的替换

可能你不太确定这是什么意思

其实只要再将上面的两段代码”翻译”一下, 你就能恍然大悟

  1. C
     #include <stdio.h>
     
     #define DOUBLE_FLOAT    double
     #define PI              3.1415926
     
     int main() {
         DOUBLE_FLOAT pi = PI;
         printf("pi: %lf\n", pi);
     
         return 0;
     }

    这段代码, 在实际编译时, 我们定义的宏 会被这样处理:

    C
    #include <stdio.h>
    
    int main() {
        double pi = 3.1415926;
        printf("pi: %lf\n", pi);
    
        return 0;
    }

    即, DOUBLE_FLOAT会变成double, PI会变成3.1415926, 且两句#define会消失

    这就是文本的替换

    这段代码好像不够直观, 我们再写一段代码:

    C
    #include <stdio.h>
    
    #define INT_STR "int value: %d\n"
    
    int main() {
        printf(INT_STR, 5);
    
        return 0;
    }

    这段代码的编译运行结果是:

    这段代码在编译时, 我们定义的宏, 会被这样处理:

    C
    #include <stdio.h>
    
    int main() {
        printf(("int value: %d\n"), 5);
    
        return 0;
    }

    即, 代码中实际用到INT_STR宏的地方, 都会被替换成("int value: %d\n")

    这不是代码语法或语义层面的替换, 而仅仅是文本层面的替换

    只是将源文件中的文本, 进行了替换

  2. 此时再看另一段测试代码:

    C
    #include <stdio.h>
    
    #define MULTI(x, y) ((x) * (y))
    
    int main() {
        printf("MULTI result: %d\n", MULTI(2, 2));
    
        return 0;
    }

    宏只是文本层面的替换, 那么这段代码的宏 会被处理成什么呢?

    C
    #include <stdio.h>
    
    #define MULTI(x, y) ((x) * (y))
    
    int main() {
        printf("MULTI result: %d\n", ((2) * (2)));
    
        return 0;
    }

    它会将括号()和参数完整地替换过去

    之后计算结果当然是正确的

    此时你可能有一个疑问: 这里计算两参数相加, 参数为什么要用括号包裹?

    要理解这个问题, 你就一定要牢记: C语言中的宏定义, 本质只是在文本层面做了文本替换

    那么, #define MULTI(x, y) ((x) * (y))

    如果调用时这样传参: MULTI(10 + 10, 10 + 10), 会被替换为 ((10 + 10) * (10 + 10)), 计算顺序就是: 先算10 + 10, 再算10 + 10, 然后再相乘

    如果, 宏定义时 参数不加括号: #define MULTI(x, y) (x * y), 且调用时这样传参: MULTI(10 + 10, 10 + 10), 会被替换成什么呢?

    结果已经很明显了: (10 + 10 * 10 + 10)

    这样, 很显然不符合预期, 因为计算顺序变成了: 先计算10 * 10, 然后顺序加上两个10

    这就是为什么, 之前在定义宏时, 把参数用括号括了起来

C语言中的宏定义语法, 非常简单, 本质也非常简单

不过, 要用好宏 其实并不简单

因为宏的本质只是简单的文本替换, 所以在定义宏时可能要考虑很多事情, 一个不小心可能就会造成不符合预期的情况

比如:

  1. 如果定义宏时, 你在结尾加了;, 会不会造成问题呢?

    C
    #define MULTI(x, y) ((x) * (y));
  2. 如果定义宏时, 你没有将参数用()包裹, 会不会出现问题呢?

    C
    #define MULTI(x, y) (x * y)
  3. 如果定义宏时, 你使用了没有声明的参数, 会不会出现问题呢?

    C
    #define MULTI(x, y) (a * b)

C语言的宏, 非常的重要, 语法上也非常的简单, 不过编译器提供了许多自带的宏定义 需要在开发过程中 去学习和了解


Thanks for reading!

从零接触C语言(初览)-X: 宏

周二 9月 02 2025
1780 字 · 8 分钟