c 语言预处理指令,C 语言语法系列(23):预处理指令

66b52468c121889b900d4956032f1009.png

8种机械键盘轴体对比

本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?

文章目录

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

#include 指令

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

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

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

反之,如果要先找当前文件夹下的头文件的话,我们使用引号导入。1#include "test.h"

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

#define 指令

真·常量定义

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

2

3

4

5

6

7

8

9

10

11

12#define pi 3.14159

int (void)

{

int i;

scanf("%d", &i);

float area = pi * i * i;

printf("%.2f", area);

return 0;

}

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

骚操作

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

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26#define 定义一个整型变量 int

#define 主函数 main

#define 无参数 void

#define 输入 scanf

#define 定义一个单精度小数型变量 float

#define 圆周率 3.14159

#define 输出 printf

#define 返回 return

#define 。 ;

#define 值为 =

#define 开始 {

#define 结束 }

#define 取变量地址(a) &a

整型 主函数 (无参数)

开始

定义一个整型变量 整型变量。

输入("%d", 取变量地址(整型变量))。

定义一个单精度小数型变量 面积 值为 圆周率 * 整型变量 * 整型变量。

输出("%.2f", 面积)。

返回 0。

结束

// 程序结束

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

宏定义有时候也会背锅

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

2#define f(a,b) (a)*(b)

#define g(a,b) a*b

它们是不同的。1

2

3

4int a = 2, b = 3;

int c, d;

c = f(a + a, b + b); // (2 + 2) * (3 + 3)

d = g(a + a, b + b); // 2 + 2 * 3 + 3

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

#undef 指令

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

2

3

4

5

6

7

8

9

10

11

12#define pi 3.14159

int ()

{

int r = 6;

int area = pi * r * r;

#undef pi

int c = 2 * pi * r; // Wrong.

return 0;

}

#define 符号指令

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

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

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

#if、#else、#elif 和 #endif 指令

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

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

2

3

4

5#if defined __cplusplus

#define NULL 0

#else

#define NULL ((void *)0)

#endif

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

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

2

3

4

5#ifdef __cplusplus

#define NULL 0

#else

#define NULL ((void *)0)

#endif

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

2

3

4

5#ifdef !__cplusplus

#define NULL ((void *)0)

#else

#define NULL 0

#endif

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

2

3

4

5#ifndef __cplusplus

#define NULL ((void *)0)

#else

#define NULL 0

#endif

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

2

3

4

5

6

7

8

9#ifdef __cplusplus

#ifndef NULL

#define NULL 0

#endif

#else

#ifndef NULL

#define NULL ((void *)0)

#endif

#endif

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

#pragma 杂注指令

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

#pragma warning 指令

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

2int a;

scanf("%d", &a);

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

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

2

3

4

5

6

7

8

9int a;

if (scanf("%d", &a) != 1)

{

// May have bugs...

}

else

{

// Successful.

}

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

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

如果你把代码改为1#pragma warning (restore: 6031)

表示从此处开始往下的代码,重新又开始对 C6031 忽略返回值的警告报错。1#pragma warning (once: 6031)

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

#pragma region 和 #pragma endregion 指令

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

#pragma once 指令

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值