define本质上是将文本内容在编译时进行替换,又因为他可以替换参数所以就出现了宏。
#define 替换文本
语法:#define name stuff
举一个例子
#define MAX 1000
#define reg register
#define do_forever for(;;)
#define CASE break;case
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ ,\
__DATE__,__TIME__ )
我们C语言的程序员内部有默契,我们把#define 定义的一般做全大写函数命名一般不是全大写一般是首字母大写或其他部分大写,当然我们宏定义假做成函数也会没有全大写。
#define定义宏
#define允许把参数替换到文本中,这种实现通常被称为宏,或定义宏
define name(由逗号隔开的符号) stuff
举例:
#include<stdio.h>
#define SQUARE(x) x*x
int main()
{
printf("%d", SQUARE(3));
return 0;
}
![[202201210943514.png]]
值得注意的是: 上面的宏其实有一个很大的弊病.
我们用下面的代码来说明:
#include<stdio.h>
#define SQUARE(x) x * x
int main()
{
printf("%d", SQUARE(3+2));
return 0;
}
![[202201210945131.png]]
这并不是我们想要的答案我们想得到5*5可是这个宏定义给的式子却给了我们11这是因为我们在定义宏时没有考虑到运算的优先级.
首先我们的宏在编译阶段会直接和代码替换本次的宏就将printf函数内容进行了替换使SQUARE(3+2)替换成了3+2*3+2这样我们就得到了11的值。
所以使用宏的时候一定要加括号!加括号!
#include<stdio.h>
#define SQUARE(x) ((x) * (x))
int main()
{
printf("%d", SQUARE(3+2));
return 0;
}
就不会出现以上情况了。
** #define 替换规则 ** 在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
- 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
- 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
- 传递给函数宏的参数不能包含看似预处理指令的标记。
#define A 100
printf("A");
"#define A 100"
字符串宏常量
#define PATH1 hello!
#define PATH1 "C:\\Users\\Desktop\\C语言"
注:可以使用反斜杠进行续行。
用宏定义充当注释符号
直接上结论: ** 是先执行去注释,在进行宏替换。** (可以去linux平台上试一试)
#define BSC
int main()
{
BSC printf("hello world\n");
return 0;
}
hello world
int main()
{
printf("hello world\n");
return 0;
}
倘若,先执行宏替换,那么先得到的代码应该是。
int main()
{
return 0;
}
int main()
{
return 0;
}
但实际并非如此。 所以是先执行去注释,在进行宏替换。
#define BSC
int main()
{
BSC printf("hello world\n");
return 0;
}
再来一个例子:
#define BSC
#define BMC
int main()
{
BSC printf("hello world\n");
BMC printf("hello bit\n"); EMC
return 0;
}
int main()
{
printf("hello world\n");
printf("hello bit\n"); EMC
return 0;
}
宏定义中的空格
#define INC(a) ((a)++)
int main()
{
int i = 0;
INC (i);
printf("%d\n", i);
}
#和##
介绍这俩之前我们需要先知道
char* p = "hello ""bit\n";
printf("hello"" bit\n");
printf("%s\n", p);
他们的打印结果都是"hello bit"这是因为字符串有自动连接的特点.
- 字符串的自动连接
- # 把一个宏参数变成对应的字符串。
- ##将两个符号合成为一个符号
介绍一下# 操作直接上例子:
#define PRINT(x) printf("The "#x" value is %d",x)
int main()
{
int a = 10;
PRINT(a);
return 0;
}
![[Pasted image 20220227161626.png]] 这样define里的#x和"x"一样.
#define PRINT(x,y) printf("The "#x" value is" #y,x)
##的作用把两边的符号合成一个符号.
#define ADD_TO_SUM(num, value) sum##num += value
int main()
{
int sum1 = 20;
int sum2 = 10;
ADD_TO_SUM(1, 20);
printf("%d", sum1);
return 0;
}
![[Pasted image 20220227161705.png]] 注: 这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。
带有副作用的宏参数
x+1;
x++;
不要对宏使用带有副作用的参数.
如下例
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
int main()
{
int x = 5;
int y = 8;
int z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);
return 0;
}
![[Pasted image 20220227162001.png]] 不然总会带来我们不想要的结果因为y进行了两次++而x进行了一次++ z得到的是y++的值也就是9当然我们知道宏MAX是什么可以轻松反推但是未来大型项目中这样搞不知会出现什么bug.
undef
移除宏定义
#undef NAME
![[Pasted image 20220227163330.png]]
2个问题:
- 宏只能在main上面定义吗?
- 在一个源文件内,宏的有效范围是什么?
#define M 10
int main()
{
# define N 100
printf("%d, %d\n", M, N);
return 0;
}
![[Pasted image 20220227162302.png]] 这个也是可以的。 结论:宏定义在哪都可以,一般放在最上面。
这样呢? ![[Pasted image 20220227162715.png]] 这样会报错。 ![[Pasted image 20220227163129.png]] 结论:宏的有效范围,是从定义处往下有效,之前无效。
一个问题
![[Pasted image 20220227063358.png]] ![[Pasted image 20220227153731.png]] 现在貌似没问题,但是 ![[Pasted image 20220227153907.png]] 如果是这样呢? 直接报错。 ![[Pasted image 20220227154019.png]] else悬空的问题,看图就能懂√
结论: 宏定义多条语句在特定场景下可能会有一些问题。
方案1: if和else带上花括号。 但是不是所有的用户都有带上花括号的习惯,为了代码的可维护性,我们还要进行修改。
方案2: 主动给宏加上花括号。 但是报错了,原因: ![[Pasted image 20220227155349.png]] 多了一个; 我们不能强迫程序员不加分号,所以方案二作废。
方案3: dowhile结构。
#define INIT_VALUE(a,b) \
do{a = 0;b = 0; }while(0)
int main()
{
int flag = 0;
scanf("%d", &flag);
int a = 100;
int b = 200;
printf("before: %d, %d\n", a, b);
if(flag){
INIT_VALUE(a,b);
}
else{
printf("error!\n");
}
printf("after: %d, %d\n", a, b);
return 0;
}
结论:当我们需要宏进行多语句替换的时候, 推荐使用do-while-zero结构。当然,如若可以,宏方面的东西,推荐少用。
宏和函数的对比
宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。
#define MAX(a, b) ((a)>(b)?(a):(b))
那为什么不用函数来完成这个任务? 原因有二:
- 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
- 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。
当然和宏相比函数也有劣势的地方:
- 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
- 宏是没法调试的。
- 宏由于类型无关,也就不够严谨。
- 宏可能会带来运算符优先级的问题,导致程容易出现错。
宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。
#define MALLOC(num, type)\
(type *)malloc(num * sizeof(type))
MALLOC(10, int);
(int *)malloc(10 * sizeof(int));
属 性 | #define定义宏 | 函数 |
---|
代码长度 | 每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长。 | 函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码 | 执行速度 | 更快 | 存在函数的调用和返回的额外开销,所以相对慢一些。 | 操作符优先级 | 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。 | 函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测。 | 带有副作用的参数 | 参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。 | 函数参数只在传参的时候求值一次,结果更容易控制。 | 参数类型 | 宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型。 | 函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的。 | 调试 | 宏是不方便调试的 | 函数是可以逐语句调试的 | 递归 | 宏是不能递归的 | 函数是可以递归的 |
命名约定
一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。
那我们平时的一个习惯是: 把宏名全部大写。 函数名不要全部大写。
|