解析C中条件编译,头文件包含知识,以及 #/## 的运用

您所在的位置:网站首页 引用头文件的作用有哪些 解析C中条件编译,头文件包含知识,以及 #/## 的运用

解析C中条件编译,头文件包含知识,以及 #/## 的运用

2024-07-12 14:13:24| 来源: 网络整理| 查看: 265

条件编译是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编译器看一下这段代码预处理后的结果,大段的头文件展开部分我们就不看了: 预处理 我们可以观察到,预处理结束后,我们的代码中只剩下了printf(“CPP\n”)这一条有效语句。 其实,条件编译的原理与宏类似,宏的基本原理是直接文本替换,而条件编译的基本原理是直接根据条件进行文本裁剪。预处理时,编译器检测到宏 CPP 被定义,就会保留 #ifdef 下面的代码,而直接将 #else 以下的代码直接裁剪。 如果我们将 #define CPP 这一行语句注释掉:

#include //#define CPP int main() { #ifdef CPP printf("CPP\n"); #else printf("NO CPP!\n"); #endif return 0; }

预处理的结果就变成了: 预处理2 ifndef 原理相同,不赘述。 总结:条件编译是在预处理阶段,直接根据条件,进行文本裁剪工作。

二. if elif else 1. 基本用法

这组概念与 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 可以被认为是判断表达式真假,CPP虽然被定义了,但它的值为0,仍被认为不满足条件。而 #ifdef 是判断宏有没有被定义,只要宏被定义了,不管它本身是何值,都满足 ifdef 的条件。 那么,读者请想一下,如果定义成空宏,结果如何?

#include #define CPP int main() { #if CPP printf("CPP\n"); #else printf("NO CPP!\n"); #endif return 0; }

会直接报错: 报错 提示为 #if 后面必须跟一个表达式。因为空宏可以看做文本替换时,替换成一个空格,而 #if 后面必须跟上一个表达式,所以报错。 但是,有趣的是,宏如果未定义,编译器会看成是假值,并不会报错,这点请读者特别注意:

#include int main() { #if CPP printf("CPP\n"); #else printf("NO CPP!\n"); #endif return 0; }

结果 还有一种用法是 #if 可以模拟 ifdef / ifndef 如:

#define CPP 0 #define C 1 int main() { #if defined(CPP) printf("CPP!\n"); #elif defined(C) printf("C!\n"); #else printf("NO!\n"); #endif return 0; }

可以用 #if defined(宏) 来模拟 #ifdef 这时候的关注点又重新回到了宏是否被定义。如该段代码的执行结果为: 结果 宏CPP虽然值为0,但其仍然被定义,故仍然满足条件。 多分支形式和 if 表达式类似,都是从上到下扫描,扫描到满足的条件就直接进入执行,以后的其他分支不再进入。 还可以用 #if !define(宏名) 来模拟 #ifndef 如:

#include #define DEBUG int main() { #if !defined(DEBUG) printf("Release\n"); #else printf("Debug\n"); #endif return 0; }

#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; }

我们看预处理的结果: 预处理 没错,在预处理阶段,编译器会帮我们根据判断条件选择相应代码保留,而那些不满足条件的代码会直接被编译器裁剪。这也是 #if 和 if 表达式的最本质的区别。 既然如此,这里有个 “丧心病狂” 的代码:

#include void SayHello() { printf("Hello World\n"); #ifndef ERROR } int main() { #endif SayHello(); return 0; }

现在编译执行这段代码并没有任何错误,因为我们没有定义宏 ERROR: 正确 可是如果我们定义这个宏(宏不一定非要在代码中定义,gcc可以使用-D选项让编译器帮我们定义这个宏): 错误 因为一旦定义了这个宏,源代码中 #ifdef 和 #endif 之间的代码将会被编译器直接裁掉,导致代码错误。 之所以举这个例子,还是希望大家明白,条件编译,仅仅是在预处理阶段,进行代码层面上的,简单的裁剪工作。

三. 条件编译的作用和意义 1.为何要有条件编译?

条件编译,其实就是编译器根据实际情况,对代码进行裁剪。而这里“实际情况”,取决于运行平台,代码本身的 业务逻辑等。 可以认为有两个好处:

可以只保留当前最需要的代码逻辑,其他去掉。可以减少生成的代码大小。可以写出跨平台的代码,让一个具体的业务,在不同平台编译的时候,可以有同样的表现。 2.条件编译都在哪些地方用?

举个例子: 我们经常听说过,某某版代码是完全版/精简版,某某版代码是商用版/校园版,某某软件是基础版/扩展版等。 其实这些软件在公司内部都是项目,而项目本质是有多个源文件构成的。所以,所谓的不同版本,本质其实就是功能的有无,在技术层面上,公司为了好维护,可以维护多种版本,当然,也可以使用条件编译,你想用哪个版本,就使用哪种条件进行裁剪就行。 著名的Linux内核,功能上,其实也是使用条件编译进行功能裁剪的,来满足不同平台的软件。

四. 文件包含 1.理解 #include

我们在敲代码的时候,大多数同学第一行语句便是 #include 那么今天,我们来聊一聊 #include 的具体作用。

#include int main() { printf("Hello World!\n"); return 0; }

我们以这段简单代码为例,看一下预处理展开的结果: 预处理 不知道大家有没有注意到,区区6行代码,预处理后竟然变成了 843 行之多,而源代码中的后五行可以看到,预处理阶段并没有进行处理,那么前面800多行代码是什么呢。 我们看预处理的第一行: 预处理 没错,原来 #include 的内容已经没有了,我们在往下翻翻: 预处理 有一些我们或认识,或不认识的函数声明。 结论:#include 在预处理阶段将头文件,递归性的,选择性的,拷贝到我们的源代码中。

关键词:

递归性的:一个头文件里可能包含着其他头文件,展开的时候会将所有的涉及到的头文件一并展开。选择性的:拷贝头文件的时候会选择性的拷贝,如原文件中的注释部分,不会拷贝到我们的源代码里。 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; }

打印结果为: # 它的作用,就是在宏中使用,把宏参 s 转换为字符串。 但是,我们不能这么用 # :

#include #define STR(x) #x int main() { int num = 123456; char* s = STR(num); printf("%s\n", s); return 0; }

大家猜猜结果是什么?结果是: # 是不是一下子感觉神秘的 # 瞬间弱智?哈哈哈,其实,这还是因为,宏在预处理阶段就被处理,而变量是在编译阶段才进行处理,所以你给 STR() 传递的 num 它在预处理阶段可不会认为是一个变量,而会直接当成一个符号,进行转字符串操作。 结论:# 与宏配合使用,会把宏参的字面值转换为字符串。

2.双井号

在宏中使用,会连接两个符号。先看一个小例子:

#include #define ADDN(x) STUDENT##X int main() { ADDN(1); ADDN(2); ADDN(3); ADDN(4); ADDN(5); }

这段代码会报错,我们看它预处理的结果: 预处理 没错,双井号会把前后两个符号连接起来,形成一个新的符号,至于为什么会报错,因为 Student1 等等并不是C的标准符号或自义定符号,编译器找不到此符号的定义。 要正确理解符号和字符串的概念:

字符串:C语言中用双引号括起来的字符组合,可以存储为变量。符号:C语言中标准符号或自义定符号,如 int a = 0 其中 int 就是一个符号。

##仅仅做的是将两个符号进行连接。

一个正确的小例子:

#include #define CONT(x,n) (x##e##n) int main() { printf("%f\n", CONT(1.1, 2)); //预处理时会被替换为 1.1e2 浮点数的科学计数法 system("pause"); return 0; }

鉴于笔者水平有限,若有大佬指出错误或不足之处,笔者感激不尽。



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭