C语言的预处理
预处理命令的介绍
C语言源文件需要经过编译、链接才能生成可执行程序
- 编译。将C语言文件转换为目标文件。对于VS/VC来说,目标文件的后缀为.obj;对于gcc来说,目标文件的后缀为.o
- 链接。将编译生成的多个文件以及系统中的库、组件等合并成一个可执行程序。
预处理:在编译之前,首先对文件进行处理的过程。
预处理主要是处理以#开头的命令。预处理命令要求放在所有函数之外,而且一般都放在源文件的最前面。所有的预命令都以#开头。
编译器会将预处理的结果保存到和源文件同名的.i文件中,并且可以用文本编辑器直接查看。
C语言提供了多种预处理功能,包括宏定义、文件包含、条件编译等,合理地使用这些命令将有助于提升程序的健壮性,使编写的程序便于阅读、修改、移指和调试,也有利于模块化程序设计。
例子:Windows系统和Linux系统下的相同函数sleep()使程序休眠5秒,Windows系统下位于windows.h文件中,Linux系统下位于unistd.h文件。
#include <stdio.h>
#if _WIN32
#include <windows.h>
#elif __linux__
#include <unistd.h>
#endif
int main(){
#if _WIN32
// Windows系统下休眠函数首字母大写且传入的参数的单位为毫秒
Sleep(5000);
#elif __linux__
// Linux系统下休眠函数首写字母小写且传入的参数的单位为秒
sleep(5);
#endif
printf("运行结束");
return 0;
}
文件包含命令#include
它有两种引用方法,分别为
-
包含的文件使用<>
它表示在编译时,编译器会到系统路径下查找头文件
-
包含的文件使用""
它表示在编译时,编译器会先在本地路径下查找该头文件;如果找不到,再去系统路径下进行查找
引用方法的应用场景
- 第一种引用方法通常用来引用标准头文件
- 第二种引用方法通常用来引用自己编写的头文件
引用的注意事项
- 一个#include只能引用一个头文件;多个头文件需要多次引用。
- 同一个头文件可以被引入多次,但是引入多与引入一次是相同的。这是因为,C语言有防止有文件被重复包含的机制。
- 文件包含允许被嵌套。即一个头文件中可以引用另一个头文件。
无论是标准的头文件,还是自己定义的头文件。头文件中只能包含函数的声明,而不能有函数的定义。
扩展:函数的声明与定义
-
函数的声明
对编译系统的一个说明:对定义的函数的返回值类型的说明,以通知系统在本函数中所调用的函数是什么类型。函数的声明是一个说明语句,必须以分号结束。
-
函数的定义
一个完整的函数单元,包含函数类型、函数名、形参及形参类型、函数体等。在程序中,函数定义有且只能有一次。
C语言的宏定义
C语言的宏定义也是C语言预处理命令的一种,宏定义表示如下
#define N 100
/* 说明
这是一条宏定义命令
N为宏名
100为宏的内容
在预处理阶段,预处理器会使用宏定义中的字符串对程序中所有出现的“宏名”进行代换,这称为“宏替换”或“宏展开”
*/
宏定义的一般形式如下
#define 宏名 字符串
/* 参数说明
宏名:标识符的一种,命名规则和变量名相同
字符串:可以是数字、表达式、if语句、函数等
*/
关于宏定义的几点说明
-
宏定义的本质是用宏名来表示一个字符串,在宏展开时,又用该字符串取代宏名。预处理程序不会对宏定义中的字符串进行任何检查,如有错误,只能在编译已被展开后的源程序时发现。
-
宏定义不是说明语句,定义的最后不用加分号。如果加上,会连同分号一起替换。
-
宏定义的默认作用域是整个源程序。如果要终止其作用域,可用命令#undef结束,命令的一般形式如下
#define name str /* 代码段 */ #undef name
-
代码中的宏名如果被引号包围,则预处理程序不会对其作宏替换
-
允许嵌套定义
-
习惯上,使用大写字母命名宏名
-
可以用来表示数据类型
与typedef定义的区别
- 宏定义只是简单的字符串替换,由预处理器完成
- typedef是在编译阶段由编译器处理的
C语言中带参数的宏定义
相当于Python的lambda表达式。在宏定义中的参数称为“形式参数”,在宏调用中的参数称为“实际参数”。
带参宏定义的一般形式如下
#define 宏名(形参列表) 字符串
带参宏定义的一般调用形式如下
宏名(实参列表)
示例
#include <stdio.h>
#define MAX(a, b) a > b ? a : b
int main()
{
int x = 10;
int y = 5;
int max_num = MAX(x, y);
printf("x和y中较大的值为%d\n", max_num);
return 0;
}
关于带参宏定义的几点说明
- 宏名与形参列表之间不可以出现空格。
- 在定义形参中,不会为形参分配内存,因此无需指明形参的类型。但是在调用宏时,需要指明传入的实参类型。
- 宏定义不同于函数,不存在值传递的问题;当传递的是一个表达式时,函数会先计算出值,再把值传递进去,而宏定义则会直接进行替换再计算。
- 为了确保程序的原含义不变,通常要对整体字符串加一个括号,也要对其中每个表示变量的字符加一个括号。
C语言中宏定义的特殊用法
使用#将宏参数转变为字符串
示例
#include <stdio.h>
#define STR(s) #s
// 这里的#s中的#会将s字符串化
int main()
{
// 特别的,如果原有的s即为字符串,则会对该字符串的引号进行转义
// printf("%s\n", STR("Hello,World"));
// 宏展开为\"Hello输出的结果为"Hello,World"
printf("%s\n", STR(Hello,World));
return 0;
}
使用##将宏参数进行连接
将宏参数与其他的串连接起来
示例
#include <stdio.h>
#define CON(a, b) a##b##00
int main(){
// 输出的结果为123400
printf("%d\n", CON(12, 34));
return 0;
}
C语言中的几个预定义的宏
各个宏的名称及用法表示如下
名称 | 类型 | 作用 |
---|---|---|
LINE | 整型 | 表示当前源代码的行号 |
FILE | 字符串型 | 表示当前源文件的名称 |
DATE | 字符串型 | 表示当前编译的日期 |
TIME | 字符串型 | 表示当前编译的时间 |
STDC | 当要求程序严格遵循ANSI C标准时,该标识符被赋值为1 |
条件编译
定义:能够根据不同情况编译不同代码、产生不同目标文件的机制称为条件编译。
#if的用法
它的一般形式如下所示
#if 表达式1
// 代码段
#elif 表达式2
// 代码段
// ...
#elif 表达式n
// 代码段
#else
// 代码段
它的用法与一般的程序语言中的if用法相同。如果表达式的值为1,则进入相应的选择结构中。
#ifdef的用法
它的一般形式如下所示
#ifdef 宏名
// 代码段
#else
// 代码段
#endif
其中的else部分可省略,但是最后的#endif不可以省略
#ifndef的用法
它的一般形式与#ifdef相同,不同之处在于:它是#ifdef的取反形式
#error指令的用法
使用方式:在编译期间产生错误信息,并阻止程序的编译
它的一般形式如下所示
#error error_message
示例
#ifdef WIN32
#error This programme cannot compile at Windows Platform
#endif
当该程序在Linux系统下进行编译的时候,就会报错并终止编译
**注:**error_message不需要加引号,如果加上,会连同引号一块输出