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