【C语言】预处理的深入理解(第二期)(下)

简介: C语言提供的条件编译的功能可以让我们按照不同的条件去编译不同的程序部分,从而产生不同目标代码文件。

2.2 防止头文件重复包含的条件编译是如何做到的?

既然我们会包含头文件,那有没有可能存在头文件重复被包含的可能性呢?导致我们头文件被重复拷贝?


这里可能会有很多老师也教过,同学们啊,我们写头文件的时候一定要写如下代码啊,这是防止头文件重复包含的啊:

#ifndef _TEST_H_
#define _TEST_H_
#include <stdio.h>
#define MAX 999
int g_val = 10;
extern void Print();
...
#endif

如上代码很多小伙伴都知道在 #ifndef _TEST_H_ 和 #endif 之间写的头文件包含,宏定义,全局变量,函数声明,都不会被重复拷贝,为什么呢?他是如何做到的?我们实验证明 (如下两张图最右边是预处理之后的结果) :

如下代码是没有带上条件编译防止头文件重复包含,但在源文件已经重复包含的例子:



我们加上 #ifndef _TEST_H_#endif 在来看重复包含的效果:



已经没有重复拷贝的情况了, 看来确实有防止头文件重复包含的效果!


那么这条语句是如何做到的呢?

我们前面学过 #ifndef 如果没有定义这个宏,则执行后续语句,当第一次我们头文件展开的时候,确实没有定义 _TEST_H_ 这个宏,所以会执行后续的语句,但是在第一次展开的时候我们立马定义了 _TEST_H_ 宏,所以我们重复包含头文件第二次展开的时候,这个宏已经被定义了,所以也就不会去执行 #ifndef 后续语句了!


结论:所有头文件都得带上条件编译,防止头文件重复包含!当然也可以直接 #pragma once  


重复包含的一定会报错吗?显然是不会的,但是会引起多次拷贝,会影响编译效率。

3、选学内容

3.1 #error 预处理

#error 预处理指令的作用是:编译程序时,只要遇到 #error 就会生成一个编译错误提示消息,并停止编译:

3.2 #line 预处理

#line 的作用时改变当前行数和文件名称,他们是在编译程序中预先定义的标识符。这里我就不给你们看运行结果了,感兴趣的可以复制代码下去自行了解下哦:

int main()
{
  printf("%s, %d\n", __FILE__, __LINE__); //C预定义符号,代表当前文件名和代码行号
#line 60 "hehe.h" //定制化完成
  printf("%s, %d\n", __FILE__, __LINE__);
  return 0;
}

本质其实是可以定制化你的文件名称和代码行号,很少使用!

3.3 #pragma 预处理

3.3.1 #pragma message

message 参数他能在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。

#define TEST
int main()
{
#ifdef TEST
#pragma message("TEST macor activated!")
#endif
    return 0;
}

当我们定义了 TEST 这个宏后,应用程序在编译时就会在编译输出窗口里显示 TEST macor activated! 因此我们就不会因为不记得自己定义的一些宏而着急了!

3.3.2 #pragma once

这个还是比较常用的,只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,但是考虑到兼容性的问题,并没有太多的使用。

3.3.3 #pragma warning

#pragma warning(disable : 4507 34; once : 4385; error : 164)
//等价于:
#pragma warning(disable : 4507 34) //不显示 4507 和 34 号警告信息
#pragma warning(once : 4385)       //4385 号警告信息仅报告一次
#pragma warning(error : 164)       //把 164 号警告信息作为一个错误

当使用 windows vs 环境的小伙伴们,在使用库函数的时候比如 scanf 会说这个函数不安全,推荐你使用 scanf_s,那我们要保证代码可以移植性如何办呢?通过查看报错发现是 4996 报错,那我们则可以:

#pragma warning(disable : 4996) //这样就解决问题了!

3.3.4 #pragma pack

设置结构体内存对齐,我们还没更新到结构体,加上用的并不算多,所以感兴趣的可以先去自行研究哦。

3.4 # 和 ##

假设说我们今天定义了一个打印宏:

#define PRINT(x) printf("hello x is %d.\n", ((x)*(x)))

调用宏 PRINT(8); 则会输出:hello x is 64.

如果你希望字符串中包含宏参数,那我们就可以使用 "#",它可以把语言符号转换成字符串:

#define PRINT(x) printf("hello "#x" is %d.\n", ((x)*(x)))

这样调用 PRINT(8); 则会输出:hello 8 is 64.

## 使用起来也很简单,就是将两个相连的符号,连接成为一个符号:

#define XNAME(n) x##n

如果这样使用宏: XNAME(8) 则会被展开成为:x8

在 "#" 或 "##" 预处理操作符相关的计算次序,如果未被指定则会产生问题,为了避免该问题,在单一的宏定义中只能使用其中一种操作符。除非是必须使用,否则尽量不适用这两个预处理操作符!

相关文章
|
2月前
|
编译器 C语言
C语言--预处理详解(1)
【10月更文挑战第3天】
|
2月前
|
编译器 Linux C语言
C语言--预处理详解(3)
【10月更文挑战第3天】
|
2月前
|
自然语言处理 编译器 Linux
【C语言篇】编译和链接以及预处理介绍(上篇)1
【C语言篇】编译和链接以及预处理介绍(上篇)
44 1
|
1月前
|
C语言
【c语言】你绝对没见过的预处理技巧
本文介绍了C语言中预处理(预编译)的相关知识和指令,包括预定义符号、`#define`定义常量和宏、宏与函数的对比、`#`和`##`操作符、`#undef`撤销宏定义、条件编译以及头文件的包含方式。通过具体示例详细解释了各指令的使用方法和注意事项,帮助读者更好地理解和应用预处理技术。
27 2
|
2月前
|
编译器 Linux C语言
【C语言篇】编译和链接以及预处理介绍(下篇)
【C语言篇】编译和链接以及预处理介绍(下篇)
37 1
【C语言篇】编译和链接以及预处理介绍(下篇)
|
2月前
|
C语言
C语言--预处理详解(2)
【10月更文挑战第3天】
|
2月前
|
编译器 C语言
C语言预处理详解
C语言预处理详解
|
2月前
|
存储 C语言
【C语言篇】编译和链接以及预处理介绍(上篇)2
【C语言篇】编译和链接以及预处理介绍(上篇)
42 0
|
4月前
|
存储 自然语言处理 程序员
【C语言】文件的编译链接和预处理
【C语言】文件的编译链接和预处理
|
4月前
|
程序员 编译器 C语言
C语言中的预处理指令及其实际应用
C语言中的预处理指令及其实际应用
96 0