C 语言的源代码到生成可运行的程序(.exe)文件,需要经过四个阶段,分别是:预处理、编译、汇编以及链接。其中预处理阶段会将源代码文件中所有的预处理指令、注释信息等内容进行处理。
什么是预处理指令?
即以 # 开头,加上相应的关键字,组合成特殊的指令。
例如最常用的文件包括指令 #include。将外部的文件引入到源代码中,然后我们就可以在源代码中使用外部文件中的函数、全局变量等等。
下面聊一聊另一个用的比较多的预处理指令,宏定义指令。
1. 什么是宏定义?
宏定义又称为宏代换、宏替换,简称为宏。是 C 语言中最为常用的三种预处理指令之一。
宏定义指令,是由 # 和关键字 define 组合而成的。
它的作用是文本替换,使用标识符来代替替换列表中的内容。使用宏定义可以提高程序的通用性、易读性,减少因为输入错误产生的各种问题,同时还便于修改。
例如:
#define PI 3.1415926
int r = PI * 3 * 3
此处定义了一个宏,PI。我在源代码中任意地方想要用到 3.141526 这个数值,就可以使用 PI 这个宏来替换。
当我需要圆周率后面更多位的数值,我只需要在宏定义的地方进行数值修改即可,就不需要一个个去找,去改。
常见的宏定义分为两种:无参宏定义和带参宏定义。
2. 无参宏定义
定义格式:
#define 标识符 替换列表
替换列表里面可以是数值常数、字符常数、字符串常数等,因此可以理解为使用标识符来表示常量,所以也被叫作符号常量。
预处理指令不是语句,因此在预处理指令的末尾是无需加上分号的。如果加上分号,可能会获得超出预期的结果。
#define N 5;
int arr[N];
虽然预处理阶段的宏定义没问题,但在编译阶段是会产生错误的。
因为宏定义只是文本替换,所以预处理的时候,会将所有的 N 替换成 5;,放到源代码中 arr[5;] 这种写法不符合 C 语言语法,所以就会产生报错。
无参宏定义还可以替换表达式,但需要注意的是,表达式需要加上括号,否则就会出现逻辑上的错误。
例如:
#define N 3+1
int a = N * N; // 3+1*3+1=7, 不符合预期结果
// 正确方式
#define N (3+1)
int a = N * N ; // (3+1)*(3+1)=16
3. 带参宏定义
定义格式:
#define 标识符(参数列表...) 替换列表
此参数列表和函数中定义的参数列表很相似,有多个参数的时候,需要用逗号隔开。不一样的是,宏定义中的参数是不需要声明参数的数据类型。
例如:定义一个求两个值哪个大的带参宏定义。
#def MAX(a,b) ((a)>(b)?(a):(b))
int r = MAX(3, 2); // r = 2
替换列表中的每个参数以及整个替换列表都需要用括号括起来,否则就可能会出现歧义。例如:
#define MUL(a,b) (a*b)
int r = MUL(3, 3+2);
这里我们希望的结果是 r = 15,但是实际结果却是 3*3+2=11。这是因为宏定义只是文本替换,将替换列表中的内容原封不动地取代宏名。
4. 删除宏定义指令
有时候先定义了宏,后面我又重新定义了和宏名一样的变量,会发生什么情况呢?
#define N 5
int N = 3;
这里编译器会给变量 N 标出红色波浪线,给出错误提示:应输入标识符。
因为宏定义了 N 为 5,那么在编译器中有出现的 N 的地方都会被替换成数字 5。所以这里就变成 5=3,很显然 5 不符合标识符的规则,就会报错。
那么该怎么解决这个问题呢?
C 语言中也提供一种预处理指令,#undef。
它的其中一个功能就是删除前面定义过的宏,解放标识符。
#define N 5
#undef N
int N = 3;
由此可以知道,宏定义的作用域是从定义开始,直到遇到 #undef 指令,或者程序运行完毕时才结束。而在作用域中,就不能再使用宏定义中的标识符。
5. 宏定义和函数的区别
学到目前,已经知道了 C 语言当中带参数的有两个,分别是宏定义和函数。
那么它们之间有什么不同之处呢?
5.1 参数类型
前面有提到一点,函数如果有参数,则必须为每一个参数指明数据类型是什么。而带参的宏定义是不需要指明参数的数据类型,因此在使用的时候可以填入任何类型的数据。
这是宏定义的优点,同样也是缺点。没有指明数据类型,就存在类型安全隐患。所以在设计宏定义的时候,程序员必须确保宏调用参数的类型正确。
5.2 运行时间
宏定义是属于预处理指令,因此发生在预处理阶段,也就是在源代码编译之前。
而函数是发生在程序运行期间的。
5.3 内存空间
宏定义只是简单的文本替换,把替换列表中的内容取代标识符的位置,替换完就会删除所有对应的标识符。因此,宏定义的参数是不需要分配空间的。
函数每次调用的时候,系统都会给形式参数重新分配一块内存空间用来存放。
5.4 执行速度
宏定义是文本替换,是不需要进行任何语法和逻辑检查,所以速度会快一些。
函数在运行阶段,参数需要经历入栈内存,然后再出栈内存的操作,所以相对的速度就会慢一些。
5.5 代码长度
如果宏定义中替换列表里面的内容是非常的长,然而预处理结束后,替换了标识符后,代码长度就会相应的变长。
函数就不会影响代码的长度。
一般建议,使用频繁且代码量小的功能可以使用宏定义。例如,比较两个数值的大小,或是计算某个数值的平方、三次方等等。
最后
以上就是关于 C 语言预处理指令宏定义的内容,希望对大家有所帮助。
有发现文中存在错谬之处,或者不理解的地方,欢迎在评论区留言,一起交流学习一下。
如果您觉得本篇文章还不错,点个赞支持一下呗!