预处理命令 #
已经多次使用过#include命令。使用库函数之前,应该用#include引入对应的头文件。这种以#号开头的命令称为预处理命令。
为什么用预处理 #
C语言源文件要经过编译、链接才能生成可执行程序:
1、编译(Compile)会将源文件(.c文件)转换为目标文件。对于 VC/VS,目标文件后缀为.obj;对于GCC,目标文件后缀为.o。
编译是针对单个源文件的,一次编译操作只能编译一个源文件,如果程序中有多个源文件,就需要多次编译操作。
2、链接(Link)是针对多个文件的,它会将编译生成的多个目标文件以及系统中的库、组件等合并成一个可执行程序。
在实际开发中,有时候在编译之前还需要对源文件进行简单的处理。例如,我们希望自己的程序在 Windows 和 Linux 下都能够运行,那么就要在 Windows 下使用 VS 编译一遍,然后在 Linux 下使用 GCC 编译一遍。但是现在有个问题,程序中要实现的某个功能在 VS 和 GCC 下使用的函数不同,(假设 VS 下使用 a(),GCC 下使用 b()),VS 下的函数在 GCC 下不能编译通过,GCC 下的函数在 VS 下也不能编译通过,怎么办呢?
这就需要在编译之前先对源文件进行处理:如果检测到是 VS,就保留 a() 删除 b();如果检测到是 GCC,就保留 b() 删除 a()。
这些在编译之前对源文件进行简单加工的过程,就称为预处理(即预先处理、提前处理)。
预处理主要是处理以#开头的命令,例如#include <stdio.h>等。预处理命令要放在所有函数之外,而且一般都放在源文件的前面。
预处理是C语言的一个重要功能,由预处理程序完成。当对一个源文件进行编译时,系统将自动调用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。
编译器会将预处理的结果保存到和源文件同名的.i文件中,例如 main.c 的预处理结果在 main.i 中。和.c一样,.i也是文本文件,可以用编辑器打开直接查看内容。
C语言提供了多种预处理功能,如宏定义、文件包含、条件编译等,合理地使用它们会使编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。
使用案例 #
要求:开发一个C语言程序,让它暂停 5 秒以后再输出内容,并且要求跨平台,在 Windows 和 Linux 下都能运行
- Windows 平台下的暂停函数的原型是
void Sleep(DWORD dwMilliseconds)(注意 S 是大写的),参数的单位是“毫秒”,位于 <windows.h> 头文件。 - Linux 平台下暂停函数的原型是
unsigned int sleep (unsigned int seconds),参数的单位是“秒”,位于 <unistd.h> 头文件。
那么就可以通过预处理命令,判断操作系统,然后引入不同的头文件,并且对逻辑也进行预处理判断
#include <stdio.h>
//不同的平台下引入不同的头文件
#if _WIN32 //识别windows平台
#include <windows.h>
#elif __linux__ //识别linux平台
#include <unistd.h>
#endif
int main() {
//不同的平台下调用不同的函数
#if _WIN32 //识别windows平台
Sleep(5000);
#elif __linux__ //识别linux平台
sleep(5);
#endif
puts("Hello World");
return 0;
}
常用预处理命令 #
| 命令 | 说明 |
|---|---|
| #define | 定义宏 |
| #include | 包含一个源代码文件,的作用相当于把stdio.h文件中的所有内容都输入该行所在的位置 |
| #undef | 取消已定义的宏 |
| #ifdef | 如果宏已经定义,则返回真 |
| #ifndef | 如果宏没有定义,则返回真 |
| #if | 如果给定条件为真,则编译下面代码 |
| #else | #if 的替代方案 |
| #elif | 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 |
| #endif | 结束一个 #if……#else 条件编译块 |
| #error | 当遇到标准错误时,输出错误消息 |
| #pragma | 使用标准化方法,向编译器发布特殊的命令到编译器中 |
#include #
#include叫做文件包含命令,用来引入对应的头文件(.h文件)。#include 也是C语言预处理命令的一种。
#include 的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源文件,这与复制粘贴的效果相同。
两种用法 #
#include <stdHeader.h>
#include "myHeader.h"
- 使用尖括号
< >,编译器会到系统路径下查找头文件,不会在当前路径下查找 - 而使用双引号
" ",编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找。
注意事项 #
- 一个 #include 命令只能包含一个头文件,多个头文件需要多个 #include 命令。
- 同一个头文件可以被多次引入,多次引入的效果和一次引入的效果相同,因为头文件在代码层面有防止重复引入的机制。
- 文件包含允许嵌套,也就是说在一个被包含的文件中又可以包含另一个文件。
#define #
#define 叫做宏定义命令,它也是C语言预处理命令的一种。所谓宏定义,就是用一个标识符来表示一个字符串(这个字符串可以是数字、表达式、if 语句、函数等),如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。
定义格式 #
#define NUM 100
#define M (n*n+3*n) //如果定义表达式,那么左右的括号不能省略
注意事项 #
- 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换。字符串中可以含任何字符,它可以是常数、表达式、if 语句、函数等,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现。
- 宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换。
- 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用
#undef命令。
带参数的宏 #
C语言允许宏带有参数。在宏定义中的参数称为“形式参数”,在宏调用中的参数称为“实际参数”,这点和函数有些类似。
对带参数的宏,在展开过程中不仅要进行字符串替换,还要用实参去替换形参。
语法:#define 宏名(形参列表) 字符串
#include <stdio.h>
#define MAX(a,b) (a>b) ? a : b
int main(){
int x , y, max;
printf("input two numbers: ");
scanf("%d %d", &x, &y);
max = MAX(x, y);
printf("max=%d\n", max);
return 0;
}
预定义宏 #
| 宏 | 描述 |
|---|---|
__DATE__ |
当前日期,一个以 “MMM DD YYYY” 格式表示的字符常量。 |
__TIME__ |
当前时间,一个以 “HH:MM:SS” 格式表示的字符常量。 |
__FILE__ |
这会包含当前文件名,一个字符串常量。 |
__LINE__ |
这会包含当前行号,一个十进制常量。 |
__STDC__ |
当编译器以 ANSI 标准编译时,则定义为 1。 |