c语言预处理命令
预处理命令简谈
使用库函数之前,应该用#include 引入对应的头文件。 这种以#号开
头的命令称为预处理命令。
预处理是 C 语言的一个重要功能,由预处理程序完成。当对一个源文件进行编译时,系统将自动调用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。
编译器会将预处理的结果保存到和源文件同名的.i 文件中,例如 main.c 的预处理结果在 main.i 中。和.c 一样, .i也是文本文件,可以用编辑器打开直接查看内容。
C 语言提供了多种预处理功能,如宏定义、文件包含、条件编译等,合理地使用它们会使编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。
#include 的用法(文件包含命令)
#include 叫做文件包含命令,用来引入对应的头文件(.h 文件)
用法如下:
1 |
使用尖括号< >和双引号” “的区别在于头文件的搜索路径不同:
使用尖括号< >,编译器会到系统路径下查找头文件;
而使用双引号” “,编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找。
stdio.h 和 stdlib.h 都是标准头文件,它们存放于系统路径下,所以使用尖括号和双引号都能够成功引入;而我们自己编写的头文件,一般存放于当前项目的路径下,所以不能使用尖括号,只能使用双引号。
例如:

my.c 所包含的代码:
1 | //计算从m加到n的和 |
my.h 所包含的代码:
1 | //声明函数 |
main.c 所包含的代码:
1 |
|
另外,不管是标准头文件,还是自定义头文件,都只能包含变量和函数的声明,不能包含定义,否则在多次引入时会引起重复定义错误。
宏定义(#define 的用法)
#define 叫做宏定义命令,它也是 C 语言预处理命令的一种。所谓宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。
例如:
1 |
|
运行结果:
120
注意第 6 行代码 int sum = 20 + N, N 被 100 代替了。
#define N 100 就是宏定义, N 为宏名, 100 是宏的内容(宏所表示的字符串)。在预处理阶段,对程序中所有出现 的“宏名”,预处理器都会用宏定义中的字符串去代换,这称为“宏替换”或“宏展开”。
宏定义是由源程序中的宏定义命令#define 完成的,宏替换是由预处理程序完成的。
宏定义的一般形式为:
1 |
#表示这是一条预处理命令,所有的预处理命令都以 # 开头。 宏名是标识符的一种,命名规则和变量相同。 字符串可以是数字、表达式、 if 语句、函数等。
1 | 这里所说的字符串是一般意义上的字符序列,不要和 C 语言中的字符串等同,它不需要双引号。 |
程序中反复使用的表达式就可以使用宏定义,例如:
1 |
它的作用是指定标识符 M 来表示(yy+3y)这个表达式。在编写代码时,所有出现 (yy+3y) 的地方都可以用 M 来表示,而对源程序编译时,将先由预处理程序进行宏代替,即用 (yy+3y) 去替换所有的宏名 M,然后再进行编译。
对 #define 用法的几点说明
(1) 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换。字符串中
可以含任何字符,它可以是常数、表达式、 if 语句、函数等,预处理程序对它不作任何检查,如有错误,只能在编
译已被宏展开后的源程序时发现。
(2) 宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换。
(3) 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef 命令。例如:
1 |
|
表示 PI 只在 main() 函数中有效,在 func() 中无效。
(4) 代码中的宏名如果被引号包围,那么预处理程序不对其作宏代替,例如 :
1 |
|
运行结果:
OK
该例中定义宏名 OK 表示 100,但在 printf 语句中 OK 被引号括起来,因此不作宏替换,而作为字符串处理。
(5) 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名,在宏展开时由预处理程序层层代换。例如:
1 |
对语句:
1 | printf("%f", S); |
在宏代换后变为:
1 | printf("%f", 3.1415926*y*y); |
(6) 习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母。
(7) 可用宏定义表示数据类型,使书写方便。例如:
1 |
在程序中可用 UINT 作变量说明:
1 | UINT a, b; |
应注意用宏定义表示数据类型和用 typedef 定义数据说明符的区别。 宏定义只是简单的字符串替换,由预处理器来处理;而 typedef 是在编译阶段由编译器处理的,它并不是简单的字符串替换,而给原有的数据类型起一个新的名字,将它作为一种新的数据类型。
请看下面的例子:
1 |
|
从形式上看这两者相似, 但在实际使用中却不相同。
下面用 PIN1, PIN2 说明变量时就可以看出它们的区别:
1 | PIN1 a, b; |
在宏代换后变成:
1 | int * a, b; |
表示 a 是指向整型的指针变量,而 b 是整型变量。然而:
1 | PIN2 a,b; |
表示 a、 b 都是指向整型的指针变量。因为 PIN2 是一个新的、完整的数据类型。 由这个例子可见,宏定义虽然也可表示数据类型, 但毕竟只是简单的字符串替换。在使用时要格外小心,以避出错。
带参数的宏定义
C 语言允许宏带有参数。在宏定义中的参数称为“形式参数”,在宏调用中的参数称为“实际参数”,这点和函数有些类似。
对带参数的宏,在展开过程中不仅要进行字符串替换,还要用实参去替换形参。
带参宏定义的一般形式为:
1 |
在字符串中可以含有各个形参。
带参宏调用的一般形式为:
1 | 宏名(实参列表); |
例如:
1 |
|
在宏展开时,用实参 5 去代替形参 y,经预处理程序展开后的语句为
1 | k=5*5+3*5。 |
对带参宏定义的说明
(1) 带参宏定义中,形参之间可以出现空格,但是宏名和形参列表之间不能有空格出现。例如把:
1 |
写为:
1 |
将被认为是无参宏定义,宏名 MAX 代表字符串(a,b) (a>b)?a:b。宏展开时,宏调用语句:
1 | max = MAX(x,y); |
将变为:
1 | max = (a,b)(a>b)?a:b(x,y); |
这显然是错误的。
(2) 在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型。而在宏调用中,实参包含了具体的数据,要用它们去替换形参,因此实参必须要指明数据类型。
这一点和函数是不同的:在函数中,形参和实参是两个不同的变量,都有自己的作用域,调用时要把实参的值传递给形参;而在带参数的宏中,只是符号的替换,不存在值传递的问题。
带参宏定义和函数的区别 :
带参数的宏和函数很相似,但有本质上的区别:宏展开仅仅是字符串的替换,不会对表达式进行计算;宏在编译之前就被处理掉了,它没有机会参与编译,也不会占用内存。而函数是一段可以重复使用的代码,会被编译,会给它分配内存,每次调用函数,就是执行这块内存中的代码。
宏参数的字符串化和宏参数的连接
在宏定义中,有时还会用到#和##两个符号,它们能够对宏参数进行操作。
# 的用法
#用来将宏参数转换为字符串,也就是在宏参数的开头和末尾添加引号。例如有如下宏定义:
1 |
那么:
1 | printf("%s", STR(c.biancheng.net)); |
分别被展开为:
1 | printf("%s", "c.biancheng.net"); |
可以发现,即使给宏参数“传递”的数据中包含引号,使用#仍然会在两头添加新的引号,而原来的引号会被转义。
##的用法
##称为连接符,用来将宏参数或其他的串连接起来。例如有如下的宏定义:
1 |
那么:
1 | printf("%f\n", CON1(8.5, 2)); |
将被展开为:
1 | printf("%f\n", 8.5e2); |
C 语言中几个预定义宏
顾名思义,预定义宏就是已经预先定义好的宏,我们可以直接使用,无需再重新定义。
ANSI C 规定了以下几个预定义宏,它们在各个编译器下都可以使用:
__LINE__:表示当前源代码的行号;
__FILE__:表示当前源文件的名称;
__DATE__:表示当前的编译日期;
__TIME__:表示当前的编译时间;
__STDC__:当要求程序严格遵循 ANSI C 标准时该标识被赋值为 1;
__cplusplus:当编写 C++程序时该标识符被定义
例如:
1 |
|
输出结果:
Date : Jul 12 2023
Time : 18:57:36
File : C:\Users\90872\Desktop\未命名1.cpp
Line : 7
条件编译
简单来说,根据不同情况编译不同代码、产生不同目标文件的机制,称为条件编译。 条件编译是预处理程序的功能,不是编译器的功能。
#if 的用法
#if 用法的一般格式为:
1 |
|
它的意思是:如常“表达式 1”的值为真(非 0),就对“程序段 1”进行编译,否则就计算“表达式 2”,结果为真的话就对“程序段 2”进行编译,为假的话就继续往下匹配,直到遇到值为真的表达式,或者遇到 #else。这一点和 if else 非常类似。
需要注意的是, #if 命令要求判断条件为“整型常量表达式”,也就是说,表达式中不能包含变量,而且结果必须是整数;而 if 后面的表达式没有限制,只要符合语法就行。这是 #if 和 if 的一个重要区别。
和 #else 也可以省略,如下所示 :
1 |
|
#ifdef 的用法
#ifdef 用法的一般格式为:
1 |
|
它的意思是,如果当前的宏已被定义过,则对“程序段 1”进行编译,否则对“程序段 2”进行编译。
也可以省略 #else:
1 |
|
#ifndef 的用法
#ifndef 用法的一般格式为:
1 |
|
与 #ifdef 相比,仅仅是将 #ifdef 改为了 #ifndef。它的意思是,如果当前的宏未被定义,则对“程序段 1”进行编译,否则对“程序段 2”进行编译,这与 #ifdef 的功能正好相反
三者之间的区别
最后需要注意的是, #if 后面跟的是“整型常量表达式”,而 #ifdef 和 #ifndef 后面跟的只能是一个宏名,不能是其他的。
#ifdef 可以认为是 #if defined 的缩写
#error 命令,阻止程序编译
#error 指令用于在编译期间产生错误信息,并阻止程序的编译,其形式如下:
1 |
例如,我们的程序针对 Linux 编写,不保证兼容 Windows,那么可以这样做:
1 |
WIN32 是 Windows 下的预定义宏。当用户在 Windows 下编译该程序时,由于定义了 WIN32 这个宏,所以
会执行 #error 命令,提示用户发生了编译错误,错误信息是:
1 | This programme cannot compile at Windows Platform |
这和发生语法错误的效果是一样的,程序编译失败。
C 语言预处理命令总结
预处理指令是以#号开头的代码行, # 号必须是该行除了任何空白字符外的第一个字符。 # 后是指令关键字,在关键字和 # 号之间允许存在任意个数的空白字符,整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。
指令 | 说明 |
---|---|
# | 空指令,无任何效果 |
#include | 包含一个源代码文件 |
#define | 定义宏 |
#undef | 取消已定义的宏 |
#if | 如果给定条件为真,则编译下面代码 |
#ifdef | 如果宏已经定义,则编译下面代码 |
#ifndef | 如果宏没有定义,则编译下面代码 |
#elif | 如果前面的#if 给定条件不为真,当前条件为真,则编译下面代码 |
#endif | 结束一个#if……#else 条件编译块 |