解析C中条件编译,头文件包含知识,以及 #/## 的运用 |
您所在的位置:网站首页 › 引用头文件的作用有哪些 › 解析C中条件编译,头文件包含知识,以及 #/## 的运用 |
条件编译是C程序预处理时进行的操作,本质是进行代码的选择性裁剪工作。指令很多,我们逐一来看。笔者建议,阅读本文前,可以了解一下宏的有关知识,具体可见博文:万能的替身演员 #define 一. ifdef / ifndef 1. 基本认识 #include int main() { #ifdef CPP printf("CPP\n"); #endif return 0; }顾名思义,ifdef 如果定义了宏CPP,执行 #ifdef 和 #endif 之间的语句,否则不执行。 还可以进行二分支: #include #define CPP int main() { #ifdef CPP printf("CPP\n"); #else printf("NO CPP!\n"); #endif return 0; }这时候,如果你定义了CPP这个宏,打印CPP,如果没定义,执行 #else 下面的内容。 了解了 ifdef 那么 ifndef 就非常简单了,它与 ifdef 在含义上恰好相反。 #include #define DEBUG int main() { #ifndef DEBUG printf("Release\n"); #else printf("Debug\n"); #endif return 0; }ifdef 是如果定义,ifndef 即 if not def 如果没有定义宏 DEBUG 执行 ifndef 下面的内容,否则,执行 #else 下面的内容。 ifdef 和 ifndef 一般不使用多分支形式,因为对于一个宏,只有定义与不定义两种情况。 2. 使用的几点注意事项与说明 ifdef 和 ifndef 后面必须跟上宏的名称,表示该宏有没有被定义,不可跟表达式。必须以 #endif 进行结尾(初学者易忘)。ifdef 和 ifndef 仅仅关注的是宏有没有被定义,而不是宏定义的值,将一个宏定义为 0 值,甚至定义为空宏,该宏也仍然被定义。 3. 原理ifdef 和 ifndef 具体是如何做的呢?我们以以下例子为例: #include #define CPP int main() { #ifdef CPP printf("CPP\n"); #else printf("NO CPP!\n"); #endif return 0; }我们切换到Linux平台,利用gcc编译器看一下这段代码预处理后的结果,大段的头文件展开部分我们就不看了: 预处理的结果就变成了: 这组概念与 ifdef/ifndef 不同,后者只能判断宏的定义与否,而前者则可以判断表达式的真假,与C语言基本语法中的 if 表达式在一定程度上比较类似。 #include int main() { #if 1 printf("True\n"); #else printf("False"); #endif return 0; }当然,它也可以使用宏进行辅助判断,只是这里和 ifdef 有一点不同的地方,如下: #include #define CPP 0 int main() { #if CPP printf("CPP\n"); #else printf("NO CPP!\n"); #endif return 0; }打印结果为: 会直接报错:
可以用 #if defined(宏) 来模拟 #ifdef 这时候的关注点又重新回到了宏是否被定义。如该段代码的执行结果为: #if 还支持多分支,嵌套使用,以及逻辑判断,这里就和C基础语法中的 if 表达式很类似了: #include //#define DEBUG #define C 0 #define CPP 1 int main() { #if defined(DEBUG) printf("Debug\n"); #if C printf("C\n"); #elif CPP printf("CPP\n"); #else printf("NO\n"); #endif #else printf("Release\n"); #if (C && CPP) //这里不带括号也行,但是带上比较优雅~ printf("C && CPP\n"); #endif #endif return 0; }大家可以自行去修改,测试这段代码,按照 if 表达式的逻辑理解就行。 2. 几点注意与说明 #if 更强调表达式的真假,而 #ifdef 只关注宏是否被定义。#if 进行判断时,若使用宏,空宏会报错,宏未定义会认为条件为假。#if defined(宏名) / #if !defined(宏名),可以模拟 #ifdef / #ifndef。#if 可以进行多分支,嵌套,判断条件可以进行逻辑运算,这里与C基础语法的 if 表达式类似。 3.原理与 #ifdef 一样,都是在预处理阶段进行代码裁剪,我们举个例子: #include #define C 1 #define CPP 1 int main() { #if defined(DEBUG) printf("Debug\n"); #if C printf("C\n"); #elif CPP printf("CPP\n"); #else printf("NO\n"); #endif #else printf("Release\n"); #if (C && CPP) printf("C && CPP\n"); #endif #endif return 0; }我们看预处理的结果: 现在编译执行这段代码并没有任何错误,因为我们没有定义宏 ERROR: 条件编译,其实就是编译器根据实际情况,对代码进行裁剪。而这里“实际情况”,取决于运行平台,代码本身的 业务逻辑等。 可以认为有两个好处: 可以只保留当前最需要的代码逻辑,其他去掉。可以减少生成的代码大小。可以写出跨平台的代码,让一个具体的业务,在不同平台编译的时候,可以有同样的表现。 2.条件编译都在哪些地方用?举个例子: 我们经常听说过,某某版代码是完全版/精简版,某某版代码是商用版/校园版,某某软件是基础版/扩展版等。 其实这些软件在公司内部都是项目,而项目本质是有多个源文件构成的。所以,所谓的不同版本,本质其实就是功能的有无,在技术层面上,公司为了好维护,可以维护多种版本,当然,也可以使用条件编译,你想用哪个版本,就使用哪种条件进行裁剪就行。 著名的Linux内核,功能上,其实也是使用条件编译进行功能裁剪的,来满足不同平台的软件。 四. 文件包含 1.理解 #include我们在敲代码的时候,大多数同学第一行语句便是 #include 那么今天,我们来聊一聊 #include 的具体作用。 #include int main() { printf("Hello World!\n"); return 0; }我们以这段简单代码为例,看一下预处理展开的结果: 关键词: 递归性的:一个头文件里可能包含着其他头文件,展开的时候会将所有的涉及到的头文件一并展开。选择性的:拷贝头文件的时候会选择性的拷贝,如原文件中的注释部分,不会拷贝到我们的源代码里。 2. #ifndef #define #endif 与 #pragma once写过头文件的同学都知道,头文件必须要有上面的两种结构之一。 这种结构的作用是:防止因头文件被重复包含而造成编译效率降低。 一般结构是这样的,假设头文件名称为test.h: #ifndef _TEST_H_ //一般这个宏这样命名是最标准的 #define _TEST_H_ /* 头文件内容: 标准头文件包含 函数声明 ... */ #endif至于为什么这样的结果可以让头文件只被包含一次,相信了解了 #include 的作用,以及条件编译的知识,大家应该清楚了。 想简单一点,可以直接在头文件第一行加上: #pragma once和上面的结构效果相同。 注:重复包含,会引起多次拷贝,主要会影响编译效率!同时,也可能引起一些未定义错误,但是特别少。 五. # / ## 1.单井号单井号一般与宏配合使用,如下: #include #define STR(x) #x int main() { char* s = STR(123456); printf("%s\n", s); return 0; }打印结果为: 大家猜猜结果是什么?结果是: 在宏中使用,会连接两个符号。先看一个小例子: #include #define ADDN(x) STUDENT##X int main() { ADDN(1); ADDN(2); ADDN(3); ADDN(4); ADDN(5); }这段代码会报错,我们看它预处理的结果: ##仅仅做的是将两个符号进行连接。 一个正确的小例子: #include #define CONT(x,n) (x##e##n) int main() { printf("%f\n", CONT(1.1, 2)); //预处理时会被替换为 1.1e2 浮点数的科学计数法 system("pause"); return 0; }鉴于笔者水平有限,若有大佬指出错误或不足之处,笔者感激不尽。 |
今日新闻 |
点击排行 |
|
推荐新闻 |
图片新闻 |
|
专题文章 |
CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭 |