前言:在c/c++学习的过程中,宏定义(#define)是作为初学者学习到的为数不多的预处理指令,在学习的时候我们被告知他可以帮助我们更高效的写程序,可以增加程序的可读性,但宏定义(#define)的功能还远远不如此,比如他还可以被当做函数一样使用,宏定义甚至能做许多函数做不到的事情,本文笔者就给大家详细解析宏定义背后的奥秘世界
目录
① 宏定义(#define)是什么
例外
② 常用宏定义用法
③ 续行操作
④ #define 定义宏
宏内部的隐患
宏外部的隐患
⑤ #define 替换规则
⑥ 带副作用的宏参数
⑦ 宏与函数的相似
# 的使用
## 的使用
⑧ 宏与函数的区别
宏的缺点:
总结
#define 可以将一对文本进行替换,在编译器读到需要被替换的文本的时候,会将这些文本全部替换成我们给定的文本,这样说还是有点抽象,我们拿一段示例在演示说明
#define A 100
#define B 200
int main()
{
int C = 0;
C = A + B;
printf("C = %d\n", C);
return 0;
}
在这里,我们将 “A”和“B” 分别使用宏定义,定义为 100和200,在以后的语句中,一旦编译器读取到 “A”和“B” 就会直接将该位置的 “A”和“B” 替换为对应的数字文本,因此 “C” 的最后值为 300
但需要注意,这里的替换文本是不包含在字符串内部的,也就是说,我们要替换的文本必须的一段完整的,他不能是一段字符串的一部分,我们还是用示例来说明一下
#define A 100
#define B 200
int main()
{
int C = 0;
C = A + B;
printf("C = %d\n", C);
int ABC = 0;
char arr[] = "ABD";
printf("ABC=%d\n", ABC);
printf("arr=%s\n", arr);
return 0;
}
我们在刚才的代码后再加一段,定义一个整形变量 ABC,定义一个字符数组 arr (内容是“ABC”) ,我们试着运行一下会发现,对于一个完整的变量或者字符串,假如他的内部的一部分字符是我们宏定义的对象的话,他是不会进行文本替换操作的
正如这里的 ABC 变量,他并没有被替换为 “100200C”,后面的字符数组也是,它并没有被替换为 “100200C”
宏定义的内容是多种多样的,可以是数字可以是字符,可以是变量,可以是遇见,也可以是函数,以下给出一些宏定义使用方法和示例
#define MAX 1000 // MAX 的大小定义为 1000
#define reg register //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把 break写上
我们重点来看最后一个,我们先写出一个普通的 switch语句:
int main()
{
int i = 0;
scanf("%d", &i);
switch (i)
{
case 1:
printf("%d\n", i);
break;
case 2:
printf("%d\n", i);
break;
case 3:
printf("%d\n", i);
break;
case 4:
printf("%d\n", i);
break;
}
return 0;
}
在加入宏定义后
#define CASE break;case //在写case语句的时候自动把 break写上
int main()
{
int i = 0;
scanf("%d", &i);
switch (i)
{
case 1:
printf("%d\n", i);
CASE 2:
printf("%d\n", i);
CASE 3:
printf("%d\n", i);
CASE 4:
printf("%d\n", i);
}
return 0;
}
每一个大写的 CASE 语句都相当于为上一个 case 自动补写了个 break,因此整个程序除了第一个 case 以外其余的全部用宏定义进行替换,我们就可以省去写 break 的步骤,但是达到同样的目的
我们知道,我们写的宏定义(#define)是一行一行的,前面是宏定义,中间是被替换的文本,最后是替换后的文本,但假如我们写了个宏定义,非常的长,一行根本写不下怎么办呢,这就需要使用续行操作符了
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ , \
__DATE__,__TIME__ )
#define 机制包括了一个规定,允许吧参数替换到文本中,这种实现通常被称为宏(macro)和定义宏(define macro),一下是宏的申明方式:
#define name (parament-list) stuff
其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在 stuff 中
以下面的代码举例:
#define SQUARE( x ) x * x
int a = 5;
printf("%d\n" ,SQUARE( a + 1) );
你觉得会是何种输出结果?
- A. 36
- B. 11
结果非常的出人意料啊,按道理来说我们计算 5+1 的平方应该是 36 啊,为什么会输出 11 呢?
我们还是从宏定义的本质上来看,宏定义是替换文本,那我们试着替换一下,替换文本时,参数 x 被替换成 a + 1,所以这条语句实际上变成了:
printf ("%d\n",a + 1 * a + 1 )
因为乘号的优先级更大,所以运行的结果就是 5+1*5+1 ,也就是 11
其实要避免这样的问题也很简单,我们在宏定义内容中加上括号就好了
#define SQUARE(x) (x) * (x)
我们再试着运行一下:
刚才我们出现的问题是来自于宏定义内部的问题,那如果是外部呢?我们还是给出一个宏定义:
#define DOUBLE(x) (x) + (x)
吸取了刚才的教训,我们提前在内容中加上了括号,我们试着运行下面的代码,看看会输出什么样的结果
int a = 5;
printf("%d\n" ,10 * DOUBLE(a));
大家可以思考思考会是何种输出:
- A. 100
- B. 55
我们试着运行一下,结果还是和我们预期的不同:
对于这样的情况,我们给整个宏定义一个括号就可以解决问题了
#define DOUBLE(x) ((x) + (x))
通过以上的学习了解,我们可以大概总结如下:
在程序中扩展 #define 定义符号和宏时,需要涉及几个步骤
- 在调用宏时,首先对参数进行检查,看看是否包含任何由 #define 定义的符号。如果是,它们首先被替换
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换
- 最后,再次对结果文件进行扫描,看看它是否包含任何由 #define 定义的符号。如果是,就重复上述处理过程
另外需注意:
x+1;//不带副作用
x++;//带有副作用
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?
我们尝试替换:
z = ( (x++) > (y++) ? (x++) : (y++));
x=6 y=10 z=9
经过上述的讲解后,笔者相信大家对宏定义应该都有了很深刻的理解,接下里我们来探讨探讨宏定义和函数的关系,如下面的代码,我们能给宏传参,能输出结果,我们可以很明确的感受到一种函数的感觉
#define PRINT(FORMAT, VALUE)\
printf("the value is "FORMAT"\n", VALUE);
PRINT("%d", 10);
我们这里还有更高级的用法:使用 “#” 可以将宏的参数变为对应的字符串
#define PRINT(FORMAT, VALUE)\
printf("the value of " #VALUE " is "FORMAT "\n", VALUE);
int i = 10;
PRINT("%d", i+3);
## 可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符
#define ADD_TO_SUM(num, value)\
sum##num += value;
ADD_TO_SUM(5, 10);//作用是:给sum5增加10.
注意:这样的连接必须产生一个合法的标识符,否则其结果就是未定义的
宏通常被应用于执行简单的运算,比如在两个数中找出较大的一个:
#define MAX(a, b) ((a)>(b)?(a):(b))
- 每次使用宏的时候,一份宏定义的代码将插入到程序中,除非宏比较短,否则可能大幅度增加程序 的长度
- 宏是没法调试的
- 宏由于类型无关,也就不够严谨
- 宏可能会带来运算符优先级的问题,导致程容易出现错
属
性
|
#define定义宏 | 函数 |
代
码
长
度
|
每次使用时,宏代码都会被插入到程序中,除了非常 小的宏之外,程序的长度会大幅度增长 |
函数代码只出现于一个地方;每 次使用这个函数时,都调用那个 地方的同一份代码
|
执
行
速
度
|
更快
|
存在函数的调用和返回的额外开销,所以相对慢一些
|
操
作
符
优
先
级
|
宏参数的求值是在所有周围表达式的上下文环境里, 除非加上括号,否则邻近操作符的优先级可能会产生 不可预料的后果,所以建议宏在书写的时候多些括号
|
函数参数只在函数调用的时候求值一次,它的结果值传递给函数,表达式的求值结果更容预 测
|
带
有
副
作
用
的
参
数
|
参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果
|
函数参数只在传参的时候求值一次,结果更容易控制
|
参
数
类
型
|
宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型
|
函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的
|
调
试
|
宏是不方便调试的
|
函数是可以逐语句调试的
|
递
归
|
宏是不能递归的
|
函数是可以递归的
|
本次分享就到此为止了,如有不不同观点,欢迎评论区讨论交流,感谢您的支持,码文不易,给个三连支持吧