【7.0】C-预处理命令
前言
预处理是在进行编译的第一遍扫描(扫描语法和词法)之前所做的工作,是C语言的一个重要功能,它由预处理程序负责完成。
前面的代码已经多次使用#
开头的预处理命令,例如:#include <stdio.h>
等,在源程序中这些命令都放在函数之外,而且一般都放在源文件的前面。
C语言提供了多种预处理命令,如宏定义,文件包含,条件编译等。C语言的预处理命令均是以#
开始,末尾不加分号。合理的使用预处理指令可以使得程序便于阅读,修改和调试。
宏定义
在C语言源程序中允许用一个标识符来表示一个字符串,称为宏。被定义为宏的标识符称为宏名。在编译预处理时,对程序中所有出现的宏名,都用宏定义的字符串去替换,这称为宏替换或者宏展开。
宏定义是由源程序中的宏定义命令完成的,宏替换是由预处理程序自动完成的。
宏定义是C语言提供的三种常用预处理命令中的一种,使用宏定义可以防止出错,并且可以提高程序的可移植性和可读性。宏分为不带参数和带参数两种。
不带参数的宏定义
不带参数的宏定义语法格式如下:
1 | //语法格式 |
其中#
表示这是一条预处理命令。define
为宏定义命令。标识符就是所谓的符号常量,也称为宏名。字符串可以是参数,表达式,格式串等。
预处理工作也称为宏展开,就是将宏名替换为字符串。掌握宏的关键就是“换”。例如:
1 |
它的作用就是指定标识符PI
来代替常量3.14。在编写程序的时候,所有的3.14都可以使用PI
来表示,而对于源程序编译的时候,将由预处理程序进行宏替换,即将PI
的部分再替换回3.14,然后再进行编译。
关于宏定义的说明:
宏名习惯上用大写字母表示,以便于与变量区别,不过你也可以使用小写
使用宏可以提高程序的拓展性和易读性。
预处理是在编译之前进行的处理,而编译的工作之一就是语法检查,也就是说,预处理不做语法检查。
宏定义不是语句,在句末不必加分号,如果加上分号,则连分号一起替换。
宏定义必须写在函数外,默认其作用域为:从宏定义命令开始到程序结束。
如果要终止其作用域可以使用
#undef
命令,例如:1
2
3
4
5
6
int main(){
//很多代码
}
//又是很多代码表示该宏定义只在
main
函数中有效。宏定义允许嵌套,在宏定义的字符串中可以使用已定义的宏名。在宏展开时由预处理程序层层替换。例如:
1
2
3
4
5
//以上宏定义的不限制顺序,例如下面的和上面的结果是一致的宏定义不分配存储空间,变量定义才分配存储空间。
宏定义以回车符结束,如果宏定义超过一行,可以在行末加反斜杠
\
来续行。宏定义中也可以没有替换的字符串,这种宏定义常作为条件编译检测的一个标志。例如:
1
【关于宏定义无字符串的意义】
【说明】这部分解释需要学会下面的条件编译命令再看
比如:
#define USEHDMI
表示定义USEHDMI
这个宏,但内容是空的,这样的宏一般不会用于替换
-用途:在程序中会这样用1
2
3
4
5\
... //宏被定义时的处理程序
\
... //宏未被定义时的程序
\这样假设我们在使用HDMI接口时会在头文件中写:
#define USEHDMI
,或写#define USEHDMI 1
也是一样的,否则用默认模式可注释此句或写:#undef USEHDMI
,即可实现程序增加处理HDMI接口的部分,或者去除。以上解释出自c++程序中宏定义只有宏名没有字符串是怎么一回事
字符,字符串和注释中永远不做宏处理,即如果在其中包含宏字符,不会进行宏替换处理。
需要注意的是,变量和宏名不可以相同
【实例】验证宏名和字符串
【代码示例】
1
2
3
4
5
6
int main(){
printf("PI %f\n", PI);
return 0;
}【输出】
带参数的宏定义
C语言允许宏带有参数。宏定义中的参数称为形参,宏调用中的参数称为实参。对于带参数的宏,在调用中,不仅要宏展开,还要用实参去代替形参。
带参数的宏定义一般形式为:
1 |
在字符串中可以含有多个形参。
带参数宏调用的一般形式为:
1 | 宏名(实参列表) |
例如:
1 |
|
【实例】利用带参数的宏定义求三个数的最小值
【代码示例】
1 |
|
【输出结果】
关于带参数的宏定义需要注意以下几点:
宏名和参数的括号间不能有空格。
宏替换只做替换,不做计算,不做表达式求解。
在宏定义中形参是标识符,而宏调用中的实参可以是表达式。
在带参数宏定义中,形参不分配存储空间,因此不必进行类型声明,而宏调用中的实参有具体的值,要用它们去替换形参,因此必须进行类型声明。
带参数的宏和带参数函数很相似,但有本质上的不同:
函数调用在编译后程序运行时进行,占用运行时间(分配内存,保留现场,值传递,返回值);宏替换在编译前进行,不分配内存,不占用运行时间,只占用编译时间。
在函数中,形参和实参是两个不同的量,各有自己的作用域,调用时要把实参传递给形参,进行“值传递”;而在带参数宏中,只是符号替换,不存在值传递。
函数只有一个返回值,利用宏可以设法得到多个返回值;
宏展开使源程序边长。
宏定义也可以用来定义多个语句,在宏调用时,把这些语句替换到源程序中。
撤销宏定义命令
宏定义命令#define
应该写在函数外面,通常写在一个文件开头,这样宏定义的作用范围是整个文件。可以使用命令#undef
撤销已定义的宏,终止该宏定义的作用域。
【实例】撤销已定义的宏示例
【代码示例】
1 |
|
【输出】
文件包含命令
文件包含是指一个源文件可以将另一个源文件的全部内容包含进来,即将另一个文件包含到本文件中。文件包含命令是以#include
开头的预处理命令,在前面的部分使用各种C语言自带的函数时已经使用了文件包含命令。在C语言中,这个命令可以使得程序分为多个模块,被不同的程序员编写。有些公用的符号常量或宏定义等可以单独组成一个文件,在其他文件的开头用文件包含命令包含该文件即可使用。这样可以避免在每个文件开头编写这些公用量,从而节省时间,减少出错。
文件包含命令语法格式如下:
1 | //格式1 |
格式1:系统先在本程序文件所在的磁盘和路径下寻找包含文件,若找不到,再按系统规定的路径搜索包含文件。
格式2:系统仅按规定的路径搜索包含文件:在包含文件目录中查找(包含目录是由用户在设置环境是设置的),而不在源文件目录去查找。
需要注意的是:
- 一个
#include
命令只能包含一个文件,若有多个文件要包含,则需要用多个#include
命令。 - 为了避免寻找包含文件时出错,如果是包含系统头文件通常使用格式2,其他情况使用格式1
- 由于被包含文件的内容全部出现在源程序清单中,所以其内容必须是C语言的源程序清单,否则编译时会出错。
- 文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件。
- 文件包含命令还有一个很重要的功能:能将多个源程序清单合并成一个源程序进行编译。
【实例】包含多个文件合并编译
【代码示例】
1 | //文件1 |
【输出】
条件编译命令
一般情况下,源程序中所有的行都参加编译,但有时希望其中的部分内容只有在满足一定的条件下才进行编译,即对一部分内容指定编译条件,这就是条件编译。条件编译命令将决定哪些代码被编译,哪些不被编译。可将表达式的值或某个特定宏是否被定义作为编译条件。
条件编译有三种语法格式,代码如下:
格式一:
1
2
3
4
5
6//格式1
程序段1
程序段2其功能是,如果标识符已使用
#define
命令定义则对程序段1进行编译,否则对程序段2进行编译。如果没有程序段2,则可以写为:1
2
3
程序段1格式二:
1
2
3
4
5
程序段1
程序段2与格式一不同的是由
#ifdef
变为#ifndef
,它的功能是,如果标识符未被#define
命令定义,则对程序段1进行编译,否则对程序段2进行编译。【实例】进行条件编译命令
【代码示例】
1
2
3
4
5
6
7
8
9
int main(){
printf("%f", PI);
printf("PI未定义");
return 0;
}【输出】
格式三:
1
2
3
4
5
程序段1
程序段2其功能是,如果常量表达式的值为真(非0),则对程序段1进行编译,反之则对程序段2进行编译。因此可以使程序在不同条件下完成不同的功能。
【实例】测试格式三
【代码示例】
1
2
3
4
5
6
7
8
9
10
11
12
int main(){
int a, b;
scanf("%d %d", &a, &b);
printf("a加b的和为:%d", a + b);
printf("a和b的差为:%d",a-b);
return 0;
}【输出】
对于预处理命令里的标识符或者变量,和代码中定义的不通用,且名称不可重复