预处理的补充知识

news/2024/5/18 13:23:54/文章来源:https://blog.csdn.net/stephen_999/article/details/127199988

🏖️作者:@malloc不出对象
⛺专栏:《初识C语言》
👦个人简介:一名双非本科院校大二在读的科班编程菜鸟,努力编程只为赶上各位大佬的步伐🙈🙈
在这里插入图片描述

目录

  • 一、宏的补充知识
    • 1.1 宏定义充当注释符号
    • 1.2 宏定义多行代码
  • 二、命令行定义
  • 三、条件编译
    • 3.1 常见的条件编译指令
    • 3.2 #undef
      • 3.2.1 例题
  • 四、头文件的补充知识
    • 4.1 头文件的重复包含
    • 4.2 头文件被包含的方式


一、宏的补充知识

1.1 宏定义充当注释符号

demo1:重点讨论预处理阶段,去注释和宏替换(预处理指令)先后顺序问题.

首先我们来看这段代码,大家觉得最终会得出什么答案呢?
在这里插入图片描述

如果是先进行宏替换,那么printf这一行会被注释掉,所以最终我们只能看到下一条printf打印的结果;而先进行去注释的话,那么大家觉得又会出现什么情况呢?下面我们来揭晓答案,输入gcc test.c此时这条指令就默认产生了一个可执行程序a.out,接下来我们输入./a.out打开这个程序。

在这里插入图片描述
我们发现hello world被打印出来了,这说明我们的宏并没有将// 替换出来将printf注释掉。那么是先去注释还是先进行宏替换就一目了然了,在进行宏替换之前我们先将注释去掉了用空格替代,所以其实我们表现的是#define BSC 这种形式,BSC替换的是空格,所以printf打印hello world不受任何影响。

下面我们输入gcc -E test.c -o test.ivim test.i进入test.i观察文件内容与源文件test.c做一个对比,我们发现处理过后的test.c文件,宏BSC替换的是空格,并且编译器做了相应的一些处理使得printf前的空格不见了,这些细节我们就不需要关心了这时编译器做的优化处理。
在这里插入图片描述

最终我们得出一个小结论:预处理期间先执行去注释,再进行宏替换。

那么趁热打铁我们来看看下面这个例子,大家觉得会打印出什么结果呢?

在这里插入图片描述
还是一样我们输入gcc test.c来运行a.out看看得出什么答案。

在这里插入图片描述

结果我们发现运行这个程序发生了错误,error显示找不到EMC这个变量名。好了,接下来我们通过对比预处理过后的文件与test.c文件,我们之前讲过预处理阶段不进行语法语意等的检查,这个是在编译阶段进行的,所以我们能得到预处理之后的文件test.i
在这里插入图片描述

vim test.i进入test.i文件之后与test.c做对比,我们之前已经得出结论是先去注释再进行宏替换,所以细心的读者也可以发现绿色部分其实已经被注释掉了,那么我们进行去注释之后就没有EMC的替换了,BMC替换的是空格。
在这里插入图片描述

1.2 宏定义多行代码

首先我们来看这段代码,大家觉得能运行成功吗?

在这里插入图片描述

我们来运行一下,发现编译过程中出现了错误,我们打开预处理之后的文件test.i进行观察,我们发现原来是if else这里出现了问题,当if else没有带大括号时只能放一条语句,因此else不能与if成功配对导致错误。

在这里插入图片描述

在这里插入图片描述

那么既然是括号的问题我们就手动添加一个大括号,我们发现这时就能达到我们的目的了。

在这里插入图片描述

虽然我们经常讲if和else后不管有多少条语句都应该保持良好的代码习惯在后面添加大括号,但是如果有人没有这样的编码习惯呢?
那么作为一个优秀的代码编写者我们应将每一种情况考虑在内,让代码在通常任何情况下都能适用。好,既然我们定义的宏有多行代码,那么我们用大括号将这段代码括起来,这样在每次进行宏替换时我们都自动加上了大括号不用我们手动进行添加。

那么接下来看看这样能否达到我们的目的呢?这个宏比上述的宏只是多加了一个括号,并没有实质上的区别哈,只是为了方便阅读我写成了这样。

在这里插入图片描述

我们发现这样还是达不到目的,程序在编译阶段出现了错误,接下来我们进入test.i文件进行观察,我们发现是if代码块后多加了一个分号,这样else就跟if匹配不起来了。

在这里插入图片描述

那么接下来我们想的是怎么去掉这个分号,那我们直接去掉INIT_VALUE(a,b);这行代码的分号吗?虽然对于这个程序来说是能够解决问题,但是这符合我们的编码习惯吗?一条语句、表达式的结束标志应该由一个分号来完成。
那么接下来我们来看看优秀的程序员是如何解决这个问题的吧🙈🙈

在这里插入图片描述

采用do while循环就完美的解决了这个问题,不管是你本身编码习惯好还是不好,每次看到if else语句都带上花括号,还是像上图一样随便放都能达到我们的目的。

在这里插入图片描述

这次采用if else好的编码习惯来进行展示,我们发现都是没有任何问题的。接下来我们想一个问题为什么一定要采用do while语句呢?它巧妙在哪里?

在这里插入图片描述

其一,因为它自身有一个花括号可以插入多段代码;
其二,我们不想让它进行循环操作只是想让它执行一次就行了所以while里面放的是0
其三,因为do while循环是以;结束的,而这完美的解决了前面多出;的问题。
所以你明白这个do while结构巧妙之处了嘛🙈🙈

二、命令行定义

许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
例如:当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性有点用处 。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器
内存大些,我们需要一个数组能够大些。)
下面我们来看一个例子:

在这里插入图片描述

你觉得这段程序可以运行成功吗?答案肯定是不行的,因为我们的SIZE根本没有被定义,我们来编译看看:

在这里插入图片描述

那么我们怎么才能做到不修改代码然后给初始化SIZE呢?此时我们就需要用到命令行定义了,下面我们来看看:

在这里插入图片描述

我们输入指令gcc test.c -D SIZE=10,此时就在命令行给SIZE初始化了,并且也打印出了正确答案,下面我再将数组开大一点试试:

在这里插入图片描述

我们也成功的实现了我们的功能,这就是命令行定义的好处:做到不修改代码而随时可以改变我们的数值范围。

三、条件编译

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的,因为我们有条件编译指令。
比如说:调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译,在各种头文件中、大型项目中以及内核源码中我们经常会看到里面包含大量的条件编译。
例如:像我们大部分小伙伴使用的VS是免费版的,还有一种是企业版是需要收费的,那么你觉得这两者之间有什么联系没有?我们用的免费版其实很多功能和收费版是相似的,但既然有收费版肯定是有些功能是我们免费版使用不了的,那么这意味着什么?我们要设计两份源码来设计免费版和收费版吗?
并不是这样的,这样大大的增大了我们的维护成本,我们只需要设计一份源码就行了,这时候就需要用到我们的条件编译了,如果你是收费版就开放下面的功能否则就只能使用免费版的功能,这样就减少了我们的维护成本。其实说的再通俗一点:条件编译的本质就是代码裁剪。

3.1 常见的条件编译指令

1. 判断是否被定义

#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol

我们来看下面这个栗子,大家觉得能打印出东西吗?

在这里插入图片描述

答案是不能的因为DEBUG未被定义,我们来看看运行之后没有任何东西:

在这里插入图片描述

我们进入预处理过后的test.i文件中查看一番,我们发现DEBUG没有被定义那么printf就不会执行,并且我们发现这块代码在预处理之后全部用空格替换掉了,类似于注释删除:

在这里插入图片描述

接着我们定义一下DEBUG看看能不能达到目的:

在这里插入图片描述

此时果然打印出了我想对各位大佬们想说的话🙈🙈

在这里插入图片描述

其他三个也是类似的用法,我再给大家举下栗子吧,大家认为会打印出哪几句呢?

在这里插入图片描述

结果如下图所示:

在这里插入图片描述

相信大家都没有问题,不过我要提一点:以上指令只关注宏是否被定义,而不关注表达式条件是否为真假,在上图已经体现出来了,我们对比一下test.ctest.i观察一下:

在这里插入图片描述

2. 条件编译

2.
#if 常量表达式
//...
#endif3.多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif

以上俩个指令的用法跟if else语句类型,相信大家都应该明白,那么这点跟#ifdef…的区别是,它不仅关注条件表达式的真假也关注宏是否被定义。
我们来看下面栗子,大家觉得能打印成功吗?

在这里插入图片描述

我们看到报错信息是#if DEBUG之后没有表达式,接下来我们将宏进行替换一下,结果显示成功:

在这里插入图片描述

4. 嵌套指令

#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif

关于这一点就不给大家一一演示了,原理跟if else语句一样,大家下去可以自己试一试。

讲完条件编译我想问大家一个问题:为什么我们有了注释还要使用条件编译呢?注释不也一样能够起到作用吗?

这样做在测试产品时很有好处,我们可以在不同版本中测试这份代码,还可以在发布产品时,我们可以不修改代码,而是直接利用条件编译来实现对一些功能的控制,直接编译出“不执行或部分执行这些宏环境”内的代码。大家也可以看看这位大佬的博客,写的十分详细。

3.2 #undef

首先我们先来想两个问题:
1.宏只能在main上面定义吗?
2.在一个源文件内,宏的有效范围是什么?

我们来看一个例子:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

由此我们得出一个结论:源文件的任何地方,宏都可以定义,与是否定义在函数内外无关。
下面我们来看第二个例子,大家觉得它能完成替换嘛?

在这里插入图片描述

我们运行一下发现编译过程出现了错误提示,error:M没有被定义,我们vim test.i进入test.i文件查看一下,我们发现在#define下面我们的M成功的进行了替换,而上方却没有替换,所以提示M未被定义找不到M

在这里插入图片描述

在这里插入图片描述
由此,我们可以还可以得出一个结论:宏的作用范围是从定义处开始的,往后都是有效的。

下面就来介绍一下#undef这个预处理命令,它是取消宏的意思(相当于undefine),可以用来限定宏的有效范围。
大家想想下面的代码经过预处理之后将会是什么情形?

在这里插入图片描述

我们通过对比test.itest.c发现在取消宏定义之后,MN不再被编译器识别到。

在这里插入图片描述

3.2.1 例题

通过上述的例子,我们知道了undef的用法及作用,接下我们做几道题来巩固一下知识点,大家一起来做做吧,首先说明这几道题有点坑,不过你只要理解了其中一题其他的都没什么问题了🙈🙈

在这里插入图片描述

这道题的正确答案为A,小伙伴们你做对了嘛?
宏定义是在编译器预处理阶段中就完成替换了,而这时候程序还没有编译运行,所以和在不在函数内无关(此阶段还没有出现函数),替换成什么只与#define和undefine的位置有关系。看向这道题从1~11行这段区间,a已经是全部替换成10的了,12行#define才重新定义标识符,那么答案就很显然了🙈🙈

根据这个例子,我对这段代码稍微改正了一下,请大家计算一下下面这两段代码的值吧。

//代码1
#include<stdio.h>
#define a 10void foo();void foo()
{
#undef a
#define a 50
}int main()
{printf("%d ", a);foo();printf("%d ", a);}//代码2
#include<stdio.h>
#define a 10void foo();
void prin();int main()
{prin();printf("%d ", a);foo();printf("%d ", a);}void foo()
{
#undef a
#define a 50
}void prin()
{printf("%d ", a);
}

代码1打印出来的结果为50,50,代码2打印出的结果为50,10,10.大家有没有做对呢🙈🙈,下面还是来分析一遍吧.

代码1:从3~9这块区域a是被替换成10的了,而从10行到文件结束这块区域a是被替换成50的,所以打印出来的结果为50,50.
代码2:从22\~38这块区间a被替换成10,39~文件结束这块区域a被替换成50了,所以进入main函数时首先调用print函数打印的是50,剩下的两个在22~38这块区域内所以打印出来是10,10.

为了让大家更清楚整个过程,我们在Linux环境中对比预处理过后的文件test.i和源文件test.c,首先看看第一个代码:

在这里插入图片描述

第二段代码:

在这里插入图片描述

大家下来好好想想,这部分不难的🙈🙈

四、头文件的补充知识

4.1 头文件的重复包含

首先我想问大家两个问题?
1. 为何所有头文件,都推荐写入下面代码?本质是为什么?
#ifndef XXX
#define XXX
//TODO //代码
#endif
2. #include究竟干了什么?

首先我先回答第二个问题,其实我在这篇博客的预处理部分已经得出了结论,这里我就不做过多的赘述了,想了解清楚的可以移步观看,我们直接上结论:#include本质是把头文件中相关内容,直接拷贝至源文件中!

那么接下来我们来想一个问题,我们在多文件包含中有没有可能存在头文件被重复包含,乃至被重复拷贝的问题呢?

这时候我们就配合第一个问题来进行测试。首先我们来看下例子,这是我们在头文件中加上了条件编译,在源文件中重复包含两份头文件的情况:

在这里插入图片描述

我们进入test.i文件中进行查看并与test.c进行对比,发现虽然源文件中包含了俩份头文件但show函数还是只声明了一份。

在这里插入图片描述

接下来我们将头文件中的条件编译去掉源文件还是不变,看看会出现什么现象

在这里插入图片描述

我们发现去掉头文件中的条件编译之后,在test.i文件中包含了俩份show函数的声明。

在这里插入图片描述

下面我再多包含几次头文件看看还是不是出现这种情况,结果确实是重复包含了多少头文件源文件中show函数就重复声明了多少次。

在这里插入图片描述

那么我们来看看条件编译是如何防止头文件被被重复包含的吧。
在这里插入图片描述

其实还有第二种解决方法:
加#pragma once在头文件前面,读者可以自行检测一下。

那么我说了这么多,大家认为头文件被重复包含就一定是错误的吗?

答案是不会的,重复包含是会引起多次拷贝(大多是函数、全局变量等的声明),但它主要是影响编译效率!虽然也可能引起一些未定义错误,但是特别少。

4.2 头文件被包含的方式

<> 查找策略:直接去库目录下查找
" "查找策略:
1.先去代码所在的路径下查找
2.如果第一步查不到,再去库目录下查找

库文件:#include < filename> 。
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
本地文件:#include “filename”
先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。 如果找不到就提示编译错误。

注意:我们在使用库文件时也可使用" “的形式,但这样做降低了查找效率,因为” “是先去代码所在路径下去查找,而库头文件肯定是在库文件中啊,所以它又去库目录下查找。因此库文件使用” "的形式纯属多余了,假如你这个路径下的文件很多,那就大大降低了查找速率啊,而且这样也不容易区分是库文件还是本地文件了。

好了,今天的内容就分享到这里了,关于预处理部分的知识就全部给大家讲完了,觉得博主写的还不错就要点点赞哦,菜鸟需要大佬们的支持🙈🙈

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_23980.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MABSA(Multimodal Aspect-Based Sentiment Analysis)2022ACL 预训练

大致浏览&#xff0c;没有细看。 论文题目&#xff08;Title&#xff09;&#xff1a; Vision-Language Pre-Training for Multimodal Aspect-Based Sentiment Analysis 研究问题&#xff08;Question&#xff09;&#xff1a;多模态情感分析 MABSA (Multimodal Aspectased S…

黑马程序员Java零基础视频教程(2022最新Java)B站视频学习笔记-Day14-面向对象进阶02

1、权限修饰符和代码块 1.1 权限修饰符 权限修饰符&#xff1a;是用来控制一个成员能够被访问的范围的。 可以修饰&#xff1a;成员变量、方法、构造方法、内部类。 巧计举例&#xff1a; private--------私有的----------相当于私房钱&#xff0c;只能自己用 默认--------…

LVS+KeepAlived高可用负载均衡集群

内容预知 1. 高可用群集的相关知识 1. 1 高可用&#xff08;HA&#xff09;群集与普通群集的比较 普通群集 高可用群集(HA) 1.2 KeepAlive 高可用方案 1.3 KeepAlived的体系模块 1.4 Keepalived实现原理 2. 高可用群集的脑裂现象及预防措施 2.1 高可用集群的脑裂现象及其…

树莓派学习笔记

记录一下树莓派的使用,包含操作系统、linux命令、python、硬件等知识。参考《树莓派开发实战》树莓派简介及型号 树莓派(Raspberry Pi)是一款基于 Linux 系统的、只有一张信用卡大小的卡片式计算机,树莓派已经成为基于 Linux 的低成本电脑和嵌入式计算机平台这个领域中的重…

Material UI – React (2022) 版的完整教程

Material UI – React (2022) 版的完整教程 这是关于 Material UI 的最期待的课程。该课程涵盖了 Material UI 的所有组件 课程英文名&#xff1a;Material UI - The Complete Guide With React (2022) Editio 此视频教程共5.5小时&#xff0c;中英双语字幕&#xff0c;画质…

【贝塞尔曲线拟合】

贝塞尔曲线拟合问题描述拟合曲线生成过程参考程序注意事项问题描述 已知一条n阶贝塞尔曲线L(P0,P1,P2,P3,...,Pn)L(P0, P1, P2, P3, ..., Pn)L(P0,P1,P2,P3,...,Pn)&#xff08;P0P0P0为起点&#xff0c;P1P1P1为第一个控制点&#xff0c;P2P2P2为第二个控制点&#xff0c;P3P…

Mysql删除重复数据只保留一条

&#xff08;1&#xff09;以这张表为例&#xff1a; CREATE TABLE test (id varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 注解id,name varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 名字,PRIMARY KEY…

队列的顺序存储结构

说白了,就是一个数组 ,然后在两端进行操作 ,两端用首队指针和尾指针分别指向 ,然后进行相关的删除,插入操作, 目的还是模拟现实对数据的处理 ●描述队列 •数据元素data , 元素具有同一类型ElemType ,最多为MaxSize(数组容量) •当前队首front •当前队尾 rear 定义队列的数据…

RK3588安装部署openmediavault

RK3588安装部署openmediavault部署准备Debian 10 文件系统编译和获取安装 openmediavault安装基础依赖安装 openmediavault 原秘钥环添加 openmediavault 官方原安装 openmediavault 基础依赖安装 openmediavaultopenmediavault 相关资料&#xff1a; https://docs.openmediav…

YOLOX 学习笔记

笔记来源&#xff1a;https://www.bilibili.com/video/BV1jo4y1D7CF/?vd_source2ed6e8af02f9ba8cb90b90e99bd4ccee 近年来&#xff0c;目标检测的工程应用研究中&#xff0c;YOLO系列以快速响应、高精度、结构简单以及容易部署的特点备受工程研究人员的青睐。同时&#xff0c;…

3. HDFS分布式文件系统

3.1 HDFS简介 随着数据量越来越大&#xff0c;在一个操作系统存不下所有的数据&#xff0c;那么就分配到更多的操作系统管理的磁盘中&#xff0c;但是不方便管理和维护&#xff0c;迫切需要一种系统来管理多台机器上的文件&#xff0c;这就是分布式文件管理系统。HDFS只是分布…

CloudlaC是什么?

目录1. CloudIaC的简介2. 部署安装2.1 下载并解压安装包2.2 安装并启动Docker2.3 安装并启动Mysql2.4 安装并启动 Consul2.5 编辑配置文件2.6 初始化MySQL2.7 安装iaC服务2.8 启动 IaC 服务2.9 拉取 ct-worker 镜像2.10 下载前端部署包并解压2.11 安装nginx并配置2.12 访问web页…

【笔试刷题训练】day_04

选择题 C/C中各种进制的表示方法 二进制&#xff1a;在数字的末尾加b&#xff0c;如101010b 八进制&#xff1a;在数字前面加数字0&#xff0c;如0123 十进制&#xff1a;数字本身&#xff0c;如123 十六进制&#xff1a;数字前面加0x 或者 数字后面加h&#xff0c;如0x123、12…

字节跳动C++云原生二面(65min)

字节跳动C云原生二面&#xff08;65min&#xff09; 面试问题 HTTP1.0 、1.1和2.0 的区别和差异是什么 《HTTP1.0和1.1的区别》HTTP1.1 默认开启长连接&#xff08;keep-alive&#xff09; 而HTTP1.0需要添加参数&#xff0c;在一定程度上减少了建立和关闭连接的消耗和延迟HT…

AntDesign-Vue Table 查询与分页

前言 之前的增删改查小 Demo 已经快要进行到最后一步了,这节的任务是将请求数据的方式改为 分页,并且增加 分页条件查询 的功能。 页面布局 <a-table:data-source="dataSource":columns="columns":pagination="pagination" > <!-- ↑…

02 docker安装

这里写目录标题CenterOS安装使用远程镜像仓库安装设置yum远程仓库第二步&#xff1a;安装docker安装第三步&#xff1a;docker镜像加速器debian/Ubuntu安装docker官网&#xff1a;https://www.docker.com/ docker镜像库&#xff1a;https://hub.docker.com/ Docker CE&#xf…

truffle安装问题-无法加载文件

在powershell 下输入以下命令 set-executionpolicy remotesigned问题解决搜索 复制

【C语言】文件版本通讯录

文章目录文件版本通讯录一、test.c&#xff08;通讯录主干&#xff09;1.通讯录菜单的实现2.创建通讯录&#xff0c;初始化通讯录3.通讯录功能的调用二、contact.c(函数的实现)1.通讯录初始化2.查看联系人是否存在函数实现3.单个修改联系人各项的信息函数实现4.修改联系人信息目…

【PyTorch深度学习项目实战100例】—— 基于Transformer实现Twitter文本隐喻二分类 | 第43例

前言 大家好,我是阿光。 本专栏整理了《PyTorch深度学习项目实战100例》,内包含了各种不同的深度学习项目,包含项目原理以及源码,每一个项目实例都附带有完整的代码+数据集。 正在更新中~ ✨ 🚨 我的项目环境: 平台:Windows10语言环境:python3.7编译器:PyCharmPy…

[Vue] TodoList 案例

前言 系列文章目录&#xff1a; [Vue]目录 老师的课件笔记&#xff0c;不含视频 https://www.aliyundrive.com/s/B8sDe5u56BU 笔记在线版&#xff1a; https://note.youdao.com/s/5vP46EPC 视频&#xff1a;尚硅谷Vue2.0Vue3.0全套教程丨vuejs从入门到精通 文章目录前言1. 组件…