我们要知道,#define后面定义的标识符只进行替换而不进行计算,我们不能根据惯性自动给它计算了,这样可能会出错。
目录
1.关于#define
1.1#define定义标识符
1.2#define定义宏
1.3#define的替换规则
2.#和##
1.#
2.##
3.带副作用的宏参数
4.宏和函数的比较
5.命名的约定
6.#undef:可以移除宏定义
1.关于#define
1.1#define定义标识符
使用格式:#define name stuff
例如:#define MAX 1000 (这个是我们经常用到的)
#define reg register 为register创建一个简短的名字reg
#define do_forever for( ; ; ) 用更形象的符号来替换一种实现
#define CASE break;case 写case语句的时候自动把break写上
我们可以举个代码例子来理解
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> //CASE break 来举例 #define CASE break;case//它的意思就是把原来case的位置,替换为break;case int main() { int input = 0; int flag = 0; printf("请选择:>"); scanf("%d ", &input); switch (input) { case 1: flag = 1; CASE 2: flag = 2; CASE 3: flag = 3; CASE 4: flag = 4; default: break; } printf("flag=%d\n", flag); return 0; }
我们可以看到,对应CASE的位置被替换为 break;case 这样我们就不必每次都写break,因为我们有时会忘记写break,这样就很方便了,是不是很奇妙。
还有一点,在define定义标识符的时候,后面一般不要加“ ;”有时会出错误
还记得我们以前说过的悬空else吗,我们来举个例子:
#define MAX 100; int main() { int m = 0; scanf("%d", &m); if (m >= 0) m = MAX; else m = -1; printf("%d\n", m); return 0; }
1.2#define定义宏
1.#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)
2.宏的申明方式:#define name(parament_list) stuff
其中: name(parament_list)是指宏的参数
parament_list是一个由逗号隔开的符号表,它可能出现在stuff中
stuff是宏的内容
3.参数列表的左括号必须与name紧邻,如果二者之间有任何空白存在,参数列表就会被解释为stuff的一部分
我们看个例子:
#define SQUARE(x) x*x int main() { printf("%d\n", SQUARE(5)); printf("%f\n", SQUARE(5.0)); return 0; }
但是这种定义方式存在隐患,我们之前说过#define定义的内容只进行替换而不计算,如果此时,我们把5改为5+1,它的值又是多少呢?
我们不妨先猜测一下,相信大多数人都回答36,但我们试着编译一下,发现结果和我们想象中不同
#define SQUARE(x) x*x int main() { printf("%d\n", SQUARE(5+1)); return 0; }
我们发现结果是11,并不是我们想想中的36,我们来解释一下
SQUARE(5+1)就是 5+1*5+1 =11
所以此时,我们又得到了几个注意点:
在写宏时,我们要勇于加括号,防止代码中进行替换时,代码结果可能出现错误
当我们希望计算结果是一个整体时,建议整体给stuff加括号
例如:#define SQUARE(x) ((x)*(x))这样就不会出现错误了
同时,宏的参数也可以是多个
例子:
#define MAX(x,y) ((x)>(y)?(x):(y)) int main() { int m = MAX(100, 443); printf("m=%d\n", m); return 0; }
1.3#define的替换规则
虽然我们前面说define时可能已经说过了它的替换规则,但是在这里还是要给大家总结一下,方便大家总结:
在程序中扩展#define定义符号和宏时,需要涉及几个步骤:
1.在调用宏时,首先对参数进行检查,看看是否包含任何由define定义的符号,如果是,它们首先被替换
2.替换文本随后被插入到程序中原来的文本位置,对于宏,参数名被它们的值所替换
3.最后,再次对结果文件进行扫描,看看它是否包含任何由#define 定义的符号,如果是,就重复上述处理过程
注意:
1.宏参数和#define定义中可以出现其他#define定义的符号,但对于宏,不能出现递归
2.当预处理器搜索到#define定义的符号的时候,字符串常量的内容并不被搜索
2.#和##
1.#
1.#它把参数插入到字符串中
2.用#把一个宏参数变为对应的字符串
这样说可能难以理解,我们直接看代码例子:
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #define PRINT1(x) printf("the value of "#x" is %d\n",x) #define PRINT2(format,x) printf("the value of "#x" is "format" \n",x) int main() { int a = 10; int b = 20; float f = 3.14f; //#把宏参数插入到字符串中 PRINT1(a); PRINT1(b); //把宏参数变成对应的字符串 PRINT2("%f", f); return 0; }
2.##
1.##可以把位于它两边的符号合成一个符号
2.它允许宏定义从分离的文件片段创建标识符
但是我们要注意,这样的连接必须产生一个合法的标识符,否则其结果就是未定义的
看个代码例子:
#define CAT(x,y,z) x##y##z int main() { int iampig = 2023; printf("%d\n", CAT(i,am,pig)); return 0; }
3.带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险导致不可预测的后果。副作用就是表达式求值的时候出现永久性效果
#define MAX(x,y) ((x)>(y)?(x):(y)) int main() { int a = 4; int b = 6; int m = MAX(a++, b++);//带有副作用的参数 printf("a=%d b=%d m=%d \n", a, b,m); return 0; }
我们本以为打印出来的会是5,7,6.为什么会这样呢?我们来分析一下
但如果我们把它封装成函数,它就不会有这个副作用,这是因为函数会先计算再传参,和宏不一样,只替换不计算
我们看个函数例子:
int Max(int x, int y) { return (x > y ? x : y); } int main() { int a = 4; int b = 6; int m = Max(a++, b++); printf("a=%d b=%d m=%d \n", a, b,m); return 0; }
所以,接下来就引出了函数和宏的对比,让我们接着学习
4.宏和函数的比较
我们从以下七点进行比较:
1.代码长度
宏:每次使用时,宏代码都会被插入到程序中,除了非常小的宏之外,程序的长度会大幅度增长
函数:函数代码只出现于一个地方,每次使用这个函数时,都会调用那个地方的同一份代码
2.执行速度
宏:更快函数:存在函数的调用和返回的额外开销,所以相对慢一些
3.操作符的优先级
宏:宏参数的求值是在所以周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多加括号
函数:函数参数只在函数调用的时候求值一次,它的结果值传递给函数,表达式的求值结果更容易预判
4.带有副作用的参数
宏:参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果
函数:函数参数只在传参的时候求值一次,结果更容易控制
5.参数类型
宏:宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用任何参数类型
函数:函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使它们执行的任务是相同的
6.调试
宏:宏是不方便调试的
函数:函数是可以逐语句调试的
7.递归
宏:不能递归
函数:可以递归
5.命名的约定
一般来讲,函数和宏的使用语法很相似,所以C语言没有规定它们的使用语法
我们约定俗成
宏命名就大写,函数名不要全大写
但有时宏也有小写特例,我们要注意
6.#undef:可以移除宏定义
这个函数是可以移除宏定义的,我们看个例子:
#define MAX 100 int main() { printf("%d\n",MAX);//可以执行 #undef MAX //printf("%d\n",MAX);//不可执行 return 0; }
当我们#undef MAX后,在打印MXA,VS会提示我们MXA未定义,这就是它移除了宏定义。
好了,关于程序环境和预处理方面的知识,我们已经全部学完了,希望大家可以理解,关于C语言的理论方面的知识点到这里也是已经给大家全部说完了,对C语言说声拜拜。
我们下期再见!!!