C语言预处理命令
目录
¶概述
Java 语言中的 导包 import
语句在 C 中等价于 #include
语句,那么在 C 中这条语句有什么用尼?
C 语言作为经典的编译型语言,想要执行一个 C 程序,需要通过编译、链接形成一个可执行程序,常说的运行就是执行这个可执行程序文件
但是在实际开发中,文件在编译之前还需要对源文件进行简单的处理。例如,对于相同的功能,在不同操作系统可能具体实现是不同的,在 windows 上需要使用 a()
在 Linux 上需要使用 b()
,这就需要在编译之前先对源文件进行处理,保证根据检测结果,执行不同的实现方式。以上这些在编译之前先对源文件进行简单加工的过程,就是预处理过程。
处理是 C 语言的一个重要功能,由预处理程序完成。当对一个源文件进行编译时,系统将自动调用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。
编译器会将预处理的结果保存到和源文件同名的.i 文件中,例如 main.c 的预处理结果在 main.i 中。和.c 一样,.i 也是文本文件,可以用编辑器打开直接查看内容。
C 语言提供了多种预处理功能,如宏定义、文件包含、条件编译等,合理地使用它们会使编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计
¶预处理案例
由于 C 语言开发的程序和操作系统强相关(需要依赖操作系统提供的需要函数调用),所以如果现在想要实现一个简单的功能:让程序暂停 5 秒,要求在全平台都可以运行。这就是 C 语言遇到的最大障碍,跨平台苦难。因为不同操作系统提供的系统调用是不同的,这就造成了想要实现相同的功能,但是在不同环境下却需要导入不同的头文件,并且使用不同的 API。
预处理程序就可以帮助我们解决这种问题,具体的做法如下:
1 |
|
其中#if
,#elif
,#endif
就是预处理命令,它们都是在编译之前由预处理程序来执行的。上面的代码通过预处理以后,在 windows 操作系统上会变成
1 |
|
在 Linux 平台上,会变成
1 |
|
在不同的平台下,编译之前(预处理之后)源代码会变成不一样,使得程序做到与具体平台的绑定。
简而言之,预处理阶段的工作,就是把代码当成普通文本,根据设定的条件进行一些简单的文本替换,将替换后的结果再交给编译器处理。
¶#include
用法详解
#include
叫做文件包含命令,用来引入对应的头文件(.h 文件)。#include
也是 C 语言预处理命令的一种.#include 的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源文件,这与复制粘贴的效果相同。
#include 的用法有两种,如下所示:
1 |
使用尖括号< >和双引号" "的区别在于头文件的搜索路径不同:
之前一直使用尖括号来引入标准头文件,现在也可以使用双引号
1 |
stdio.h 和 stdlib.h 都是标准头文件,它们存放于系统路径下,所以使用尖括号和双引号都能够成功引入;而我们自己编写的头文件,一般存放于当前项目的路径下,所以不能使用尖括号,只能使用双引号
👴 建议养成使用尖括号来引入标准头文件,使用双引号来引入自己编写的头文件,这样一眼就能看出头文件的区别
🎶 关于 #include 用法的注意事项:
📓 最好不要在头文件中定义函数和全局变量,不管是标准头文件,还是自定义头文件,都只能包含变量和函数的声明,不能包含定义,否则在多次引入时会引起重复定义错误
¶头文件重复引用
多重包含在绝大多数情况下出现在大型程序中,它往往需要使用很多头文件,因此要发现重复包含并不容易。要解决这个问题,我们可以使用条件编译。如果所有的头文件都像下面这样编写:
1 |
|
那么多重包含的危险就被消除了。当头文件第一次被包含时,它被正常处理,符号_HEADERNAME_H 被定义为 1。如果头文件被再次包含,通过条件编译,它的内容被忽略。符号_HEADERNAME_H 按照被包含头文件的文件名进行取名,以避免由于其他头文件使用相同的符号而引起的冲突。
¶变量的重复定义
由于工程中的每个.c 文件都是独立的解释的,即使头文件有下面的预处理指令
1 |
|
在其他文件中只要包含了 global.h 就会独立的解释,然后每个.c 文件生成独立的标示符。在编译器链接时,就会将工程中所有的符号整合在一起,由于文件中有重名变量,于是就出现了重复定义的错误。使用下列方法就可以解决这个问题:
¶#define
用法
#define 叫做宏定义命令,它也是 C 语言预处理命令的一种。所谓宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串,可以理解为字符串替换操作
宏定义的一般形式为:
1 |
#表示这是一条预处理命令,所有的预处理命令都以 # 开头。宏名是标识符的一种,命名规则和变量相同。字符串可以是数字、表达式、if 语句、函数等。
¶无参数宏定义
🎶 使用宏定义要点
- 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换。字符串中可以含任何字符,它可以是常数、表达式、if 语句、函数等,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现
- 宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换
- 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef 命令
1 |
|
- 代码中的宏名如果被引号包围,那么预处理程序不对其作宏代替,例如:
1 |
|
- 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名,在宏展开时由预处理程序层层代换
1 |
- 习惯上宏名用大写字母表示,以便与变量区别,但也允许用小写字母
- 可用宏定义表示数据类型,使书写方便
¶带参数宏定义
C 语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数,这点和函数有些类似,对带参数的宏,在展开过程中不仅要进行字符串替换,还要用实参去替换形参。
带参宏定义的一般形式为:
1 |
在字符串中可以含有各个形参,而带参宏调用的一般形式为:
1 | 宏名(实参列表); |
例如:
1 |
|
🎶 带参宏定义注意要点
带参数的宏和函数很相似,但有本质上的区别:
¶条件编译
假如现在要开发一个 C 语言程序,让它输出红色的文字,并且要求跨平台,在 Windows 和 Linux 下都能运行,这个程序的难点在于,不同平台下控制文字颜色的代码不一样,我们必须要能够识别出不同的平台。
Windows 有专有的宏_WIN32,Linux 有专有的宏linux,以现有的知识,使用 if else 判断一下就可以了呀
1 |
|
但是这段代码是错误的,在 Windows 下提示 linux 是未定义的标识符,在 Linux 下提示 _Win32 是未定义的标识符。对上面的代码进行改进:
1 |
|
#if、#elif、#else 和 #endif 都是预处理命令,根据不同的宏定义,保留不同的代码片段。这些操作都是在预处理阶段完成的,多余的代码以及所有的宏都不会参与编译,不仅保证了代码的正确性,还减小了编译后文件的体积。
这种能够根据不同情况编译不同代码、产生不同目标文件的机制,称为条件编译。条件编译是预处理程序的功能,不是编译器的功能。
¶#if
#if 用法的一般格式为:
1 |
|
它的意思是:如常“表达式 1”的值为真(非 0),就对程序段 1进行编译,否则就计算表达式 2,结果为真的话就对程序段 2进行编译,为假的话就继续往下匹配,直到遇到值为真的表达式,或者遇到 #else。这一点和 if else 非常类似。
🎶 #if
命令要求判断条件为整型常量表达式,也就是说,表达式中不能包含变量,而且结果必须是整数;而 if 后面的表达式没有限制,只要符合语法就行。这是 #if 和 if 的一个重要区别
¶#ifdef
1 |
|
它的意思是,如果当前的宏已被定义过,则对程序段 1 进行编译,否则对程序段 2 进行编译,其中#else
片段也可以省略
¶#ifndef
1 |
|
与 #ifdef 相比,仅仅是将 #ifdef 改为了 #ifndef。它的意思是,如果当前的宏未被定义,则对程序段 1 进行编译,否则对程序段 2 进行编译,这与 #ifdef 的功能正好相反。
¶条件编译总结
📓 #if 后面跟的是整型常量表达式,而 #ifdef 和 #ifndef 后面跟的只能是一个宏名,不能是其他的
¶总结
预处理指令是以#号开头的代码行,# 号必须是该行除了任何空白字符外的第一个字符。# 后是指令关键字,在关键字和 # 号之间允许存在任意个数的空白字符,整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。
指令 | 说明 |
---|---|
# | 空指令,无任何效果 |
#include | 包含一个源代码文件 |
#define | 定义宏 |
#undef | 取消已定义的宏 |
#if | 如果给定条件为真,则编译下面代码 |
#ifdef | 如果宏已经定义,则编译下面代码 |
#ifndef | 如果宏没有定义,则编译下面代码 |
#elif | 如果前面的#if 给定条件不为真,当前条件为真,则编译下面代码 |
#endif | 结束一个#if……#else 条件编译块 |
为了避免宏代换时发生错误,宏定义中的字符串应加括号,字符串中出现的形式参数两边也应加括号。使用预处理功能便于程序的修改、阅读、移植和调试,也便于实现模块化程序设计。
¶附录
C 语言预处理命令是什么?
C 语言#define 的用法,C 语言宏定义
C 语言#if、##ifdef、#ifndef 的用法详解,C 语言条件编译详解
多文件编程
如何进行 C 语言多文件编程
C 语言多文件编程基本格式
C 语言基础:多文件编译
头文件重复引用