如果你从未接触过C语言, 那么我建议你先阅读前面的文章:
宏
宏是一种十分通用的思想, 核心思想是预录制复杂操作, 然后实现 用简单的指令自动执行复杂操作
如果你十分喜欢玩游戏, 你可能听说过一种宏: 鼠标宏 或 键盘宏
鼠标宏或键盘宏 可以实现对鼠标、按键一系列操作的预录制, 然后将预录制完成的操作绑定到目标按键上, 之后只需要按下一个按键就能够按照预录制好的操作, 完整执行
如果你能够熟练使用Excel, 你或许使用过其中的录制宏的功能
Excel提供有录制宏的功能, 可以将表格操作(数据格式化、复制粘贴、数据计算等操作)进行录制, 录制完成之后可以自动转换为VBA宏, 在之后 可以直接使用宏完成相同的数据操作
这就是宏的思想
C语言, 按照宏的思想, 实现了一种语法简单的宏定义功能
#define
#define
就是C语言标准提供的宏定义语法
C语言标准提供的宏定义语法非常的简单:
#define 标识符 替换内容
只需要简单拿的一句话, 就能完成一个宏的定义
不过, 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_FLOAT
与 PI
我们尝试使用DOUBLE_FLOAT
定义变量, 并尝试给变量赋值为PI
结果表明, 这个尝试成功了
这就是C语言中宏最简单的使用: 可以使用宏, 对目标数据、关键字等 “重新起一个标识符名字”
带参数的宏
#define
定义宏, 最简单的用法就是:
#define 标识符 替换内容
但, #define
还可以定义带参数的宏:
#define 标识符(参数列表) 替换内容
定义带参数的宏时, 替换内容部分有些不同: 此时, 替换内容中, 只能包含参数列表中的参数(可以不包含参数)
还是通过一段简单的代码理解:
#include <stdio.h>
#define MULTI(x, y) ((x) * (y))
int main() {
printf("MULTI result: %d\n", MULTI(2, 2));
return 0;
}
这段代码, 我们定义了一个带参数的宏MULTI(x, y)
, 尝试去实现x * y
的操作
不过代码中好像多做了一些看起来没有用的操作: ((x) * (y))
, x
和y
被括号括起来了
不过, 尝试成功了
这两次#define
的使用, 或许你会发现:
第一次使用, #define
和typedef
好像
因为, 看来去#define
可以给数据类型起一个另外的名字使用, 而typedef
实际就是做这个的
第二次使用, #define
和函数好像
因为, 函数也可以通过类似标识符(参数)
的方式, 执行目标操作
但, 事实上 它们是完全不同的
究竟怎么个不同, 只要了解了#define
的本质, 就可以理解了
#define
的本质 **
C语言中, #define
的本质其实非常的简单: 文本的替换
可能你不太确定这是什么意思
其实只要再将上面的两段代码”翻译”一下, 你就能恍然大悟
#include <stdio.h> #define DOUBLE_FLOAT double #define PI 3.1415926 int main() { DOUBLE_FLOAT pi = PI; printf("pi: %lf\n", pi); return 0; }
这段代码, 在实际编译时, 我们定义的宏 会被这样处理:
#include <stdio.h> int main() { double pi = 3.1415926; printf("pi: %lf\n", pi); return 0; }
即,
DOUBLE_FLOAT
会变成double
,PI
会变成3.1415926
, 且两句#define
会消失这就是文本的替换
这段代码好像不够直观, 我们再写一段代码:
#include <stdio.h> #define INT_STR "int value: %d\n" int main() { printf(INT_STR, 5); return 0; }
这段代码的编译运行结果是:
这段代码在编译时, 我们定义的宏, 会被这样处理:
#include <stdio.h> int main() { printf(("int value: %d\n"), 5); return 0; }
即, 代码中实际用到
INT_STR
宏的地方, 都会被替换成("int value: %d\n")
这不是代码语法或语义层面的替换, 而仅仅是文本层面的替换
只是将源文件中的文本, 进行了替换
此时再看另一段测试代码:
#include <stdio.h> #define MULTI(x, y) ((x) * (y)) int main() { printf("MULTI result: %d\n", MULTI(2, 2)); return 0; }
宏只是文本层面的替换, 那么这段代码的宏 会被处理成什么呢?
#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语言中的宏定义语法, 非常简单, 本质也非常简单
不过, 要用好宏 其实并不简单
因为宏的本质只是简单的文本替换, 所以在定义宏时可能要考虑很多事情, 一个不小心可能就会造成不符合预期的情况
比如:
如果定义宏时, 你在结尾加了
;
, 会不会造成问题呢?#define MULTI(x, y) ((x) * (y));
如果定义宏时, 你没有将参数用
()
包裹, 会不会出现问题呢?#define MULTI(x, y) (x * y)
如果定义宏时, 你使用了没有声明的参数, 会不会出现问题呢?
#define MULTI(x, y) (a * b)
…
C语言的宏, 非常的重要, 语法上也非常的简单, 不过编译器提供了许多自带的宏定义 需要在开发过程中 去学习和了解