前几天看到有博主写了C语言的骚操作,点开看后是一个之前没时间去写的题目,想想要不还是补充一些对方没有讲到的内容。所以,本篇将会主要讲一下预编译指令中的#include以及#define的本质用法,并且衍生出一个小小的C语言骚操作。
#include预编译指令
众所周知#include“xxx.h”的作用是用来包含头文件的,作用是能够调用头文件中的各类枚举/结构体/函数定义等。那么一个C文件是如何通过#include“xxx.h”文件就能编译到那些定义呢?那就要从编译器眼中的#include指令说起。
长话短说,
大家可以把#include“xxx.h”看成是文本的展开,简而言之就是通过该指令把h文件里的内容进行了展开
。如下范例所示,我们定义一个H文件。
//这是一个命名为test.h的H文件
#ifndef TEST_H
#define TEST_H
typedef enum
{
Test_item_1,
Test_item_2,
Test_item_3
}e_Test;
#endif
//这是一个命名为test.c的c文件
//为了调用枚举e_Test,所以需要调用test.h头文件
//在程序猿们的眼中,这是一个h文件被调用的样子
#include "test.h"
void func1()
{
....
}
//在编译器的眼中,#include调用H文件其实是文本的展开
//这也是为什么很多头文件的开头都是先来一句#ifndef,否则在同一H文件被多处调用的时候,该文件中的定义就会被重复编译,引起报错
#ifndef TEST_H
#define TEST_H
typedef enum
{
Test_item_1,
Test_item_2,
Test_item_3
}e_Test;
#endif
void func1()
{
....
}
#define预编译指令
顾名思义,这个指令的含义是“定义”,大家可以把该指令视为一种文本的替换。详细在下面代码展示。
//使用该指令定义了一个数
#define PI 3.14
//那么大家在后面的调用中就是直接使用PI就可以了
float Circle_Area = PI * r*r;
//实际在编译器编译的时候就会把字符PI替换成3.14,所以在编译器眼中实际上上述等式的样子如下
float CirCle_Area = 3.14 * r*r;
//再例如有时候大家喜欢写一些宏定义“函数”
#define MIN(x,y) (x>y)?x:y; //两个数比大小
//在程序猿们的眼里,代码编写如下
uint8_t num_A = 3;
uint8_t num_B = 6;
uint8_t min_num;
min_num = MIN(num_A,num_B);
//但是在编译器的眼中这段代码如下
min_num = (num_A<num_B)?num_A:num_B;
//而如果是仅仅是做了一个定义,它并不是毫无意义的,编译器会把这个定义作为一个标识符暂时存放,表明该字符已经进行过了定义声明。
#define FUNC_ENABLE //编译器就会把这个字符串认为是一个已经定义的标识符
预编译文本展开的骚操作
如下面的代码所示,通过结合#define和#include,就可以实现灵活的从H文件中拿到特定场景下所需的目标数据。
//这是名为test.h的头文件
#ifdef GetAddr //如果有定义了GetAddr,那么如下字符则进行定义
"Here is China"
#undef GetAddr //取消对GetAddr的定义
#endif
#ifdef GetName //如果有定义了GetName,那么如下字符则进行定义
"BugMaker"
#undef GetName //取消对GetName定义
#endif
#include
uint8_t Address[] =
{
#define GetAddr //预编译中定义GetAddr
#include "test.h" //在此处展h文件
};
uint8_t Name[]=
{
#define GetName //预编译中定义GetName
#include "test.h" //在此处展h文件
};
int main() {
printf("%s\r\n",Address); //输出Address数组内容
printf("%s\r\n",Name); //输出Name数组内容
return 0;
}
运行结果如下:
这个操作其实不算是小众,针对某一模块的数据统一管理,这是一种值得参考的写法。例如在著名的LWIP(Light Weight IP轻量级IP协议栈)中就有使用。不过这种做法一定程度上降低了代码的可读性,大家视情况使用,否则可能会适得其反。