欢迎光临散文网 会员登陆 & 注册

第 24 讲:预处理指令

2021-10-04 03:27 作者:SunnieShine  | 我要投稿

编译器指令,或者叫做预处理指令(Preprocessor),指的是在程序用编译器翻译为机器可以识别和执行的指令前,所做的指令操作。它可以控制程序在不同执行状态下执行不同的结果。为区分其他编程语句,我们使用井号 #进行开头。


#include 指令

这个指令用于导入头文件。什么是头文件呢?在使用特定的编程函数,诸如 printfscanf 等时,由于每一个编程函数都有不同的功能,所以为了方便管理,它们各自被放在不同的头文件下,在使用时,需要根据它本身属于哪个头文件,先要导入,才能使用。否则编译器找不到这个函数。

它有两种导入方式:引号导入和尖括号导入。如果写成这样:

则表示它是一个系统自带的编程函数。它会先找系统自带的函数库,发现文件存在就可以打开。如果不存在,再找到你建立这个C语言程序代码下,文件夹下是否有这个头文件。

反之,如果要先找当前文件夹下的头文件的话,我们使用引号导入。

注意,编译器指令都是不用分号结尾的


#define 指令

真·常量定义

它又被称为宏定义(Macro Defining),是将代码里所有代码的写法替换成宏定义过的写法。比如,在代码里写很多计算圆的公式,它会大量使用到圆周率的值(约 3.14159),可如果代码内直接写值的话,如果需要对代码作改动,就需要挨个改掉它们,就相对麻烦,这个时候我们可以使用 #define 指令替换它们,然后改值时,只需要改 #define 一处的值即可。

另外,一般而言,像是这样的指令写的东西(3.14159)是一个常数,所以我们一般将常数全部大写变量名,即 PI 的形式,不过这只是一般而言,也可以不大写。


骚操作

不过,宏定义下的写法是直接替换的,也就是说,你这么写完全可以:

只是,一部分 IDE,例如 DevCpp 的编译器不能执行中文字符的宏定义替换罢了。不过,#define 定义语句中间的空格是严格规定的,只能在 #define 后和定义名称后有空格,这样来控制替换,否则系统会无法判别。


宏定义有时候也会背锅

另外,#define 有一处需要注意的替换。它甚至可以替换函数,但函数的替换方式可能有一些恶心:

它们是不同的。

看懂了吗?上面的 ab 都有括号,所以下面替换后,也带括号;上面没有括号,下面也没有括号。于是根据优先级的不同,计算结果分别是 4 * 6 = 24 和 2 + 6 + 3 = 11。

#undef 指令

甚至还可以使用 #undef 定义名 的方式来撤销结束定义。比如

#define 符号指令

我们也可以在定义符号的时候不为其赋值:

这样定义的符号在本文件里的任意位置都可以用,或你把它写进头文件里,然后对其使用了 #include 引入了这个头文件时可用。

这种用法需要配合下面要说到的 #if 指令才能发挥作用。


#if#else#elif#endif 指令

这四个指令单纯就是为了和 C++ 语言配合使用得比较多。在 C++ 里,有些常量的定义数值是和 C 不一致的,好在它提供了 #define 的宏指令指定了语言类别。

在 C++ 里,系统定义了 __cplusplus 符号。如果我们查看 __cplusplus 符号是否存在,就可以判断当前使用的文件和语言是 C 语言还是 C++,进而去区分程序的执行代码。

例如上面的 5 行指令,先查询当前代码环境是否是 C++。如果是 C++,则会执行第 2 行的 #define 定义语句,让 NULL 常量定义为 0(这是 C++ 里的定义方式。C++ 的空是用 0 表示而不是 (void *)0 表示的;否则,没有查到 __cplusplus,说明是 C 语言环境,那么 NULL 的定义方式就是(void *)0 了。

另外,#if defined 可以简写为 #ifdef 指令:

另外,你对条件取反,也是被允许的:

你也可以使用 #ifndef 指令来表达:

最后,你还可以使用嵌套的方式为其定义。实际上,NULL 常量在头文件里是这么定义的。

由于头文件 *.h 是不区分 C 还是 C++ 语言的,所以保证程序允许正常,这些指令和赋值都是不可少的。#elif 指令类似于 else if 条件判断,这里就不讲了,因为用的机会很少。


#pragma 杂注指令

最后来说一下杂注(Pragma)。杂注是预处理指令里最复杂的一种,这是因为它的名字“杂注”就表示了所有乱七八糟的指令,不好取名或用得比较少的时候,就直接用 #pragma 指令来搞了,所以它的分支也非常多。我们这里来看一些常用的。至于不常用的,如果你碰到了,直接查询网上给出的文档即可。


#pragma warning 指令

#pragma warning 指令用于忽略、恢复和只提示一次警告信息。假如你的代码里出现了警告错误,例如下面代码

这是最基础的使用 scanf 函数的方法了。不过编译器会提出警告 C6031,提示你忽略了 scanf 的返回值。scanf 的返回值表示正确输入到变量里的变量总个数。如果你输入了一个合适的数字,a 正常赋值,那么这个返回值就是 1。

它提示这个信息,提示你最好不要忽略返回值的使用,而是这样用:

但是,显然我们没有必要这么去处理,因为我们输入一个整数数值是预期的,所以我们可以忽略烦人的警告信息,用法是这样的:

这样的话,你就看不到后续使用的所有有关 scanf 上输入信息的返回值未使用的报错了。

如果你把代码改为

表示从此处开始往下的代码,重新又开始对 C6031 忽略返回值的警告报错。

这样则是仅报告一次这样的警告信息,其它的都忽略。


#pragma region#pragma endregion 指令

忘了这个了吗?这个就是最开始介绍的,用于分割语义的指令。这两个指令是配合使用的,用于分割代码段,这一段代码里的所有执行内容表示某个含义。另外,写了这样的指令后,IDE 就可以识别这一块代码是我们指定好的代码块,甚至可以允许我们手动折叠和展开它们。


#pragma once 指令

最后一个指令仅用于头文件里,表示这个头文件只会被编译一次。这是因为,很多时候,相同的指令会被分散放到不同的头文件里;而反之也可能存在多个相同名称的头文件。这样的话,如果我们使用 #pragma once 指令可以保证这个文件整体只会被编译一次,防止多次反复编译出现隐藏的 bug 和错误。

当然,如果执行逻辑都不同的同名头文件最好还是不要用一样的头文件名,防止这些错误的出现。


第 24 讲:预处理指令的评论 (共 条)

分享到微博请遵守国家法律