【C语言】预处理(下)(C语言完结篇)

一、#和##

1、#运算符

这里的#是一个运算符,整个运算符会将宏的参数转换为字符串字面量,它仅可以出现在带参数的宏的替换列表中,我们可以将其理解为字符串化。

我们先看下面的一段代码:

第二个printf中是由两个字符串组成的,这样写法的输出和第一个写法输出是否会有不同呢?

运行结果:

可以看到,这两个挨在一块的字符串会被合并起来输出。

下面我们通过一个例子来弄清楚# 符号到底是怎么个事:

我们现在想在屏幕上打印the value of a is 20 。那么我们可以如下面一样操作:

但是当我们需要对其他几个变量也打印这样的信息呢?那么是不是要重复的写这么一长串的代码,有没有什么办法可以将其简化?

此时我们可以通过宏来解决这个问题,在上面的字符串中,不同的变量发生变化的有三个地方:

一个就是of后面的,这个地方是要填入的是变量名,然后是占位符,然后就是参数了。

参数是比较好解决的,主要是变量名和占位符,这是因为这两个都是字符串,所以我们在使用宏替换的时候,是不会将其替换的,此时是不是就没办法了呢?

对于占位符的替换,我们可以用上面那个代码的情况,两个字符串是可以合并的。

还有就是对于变量名,其就需要使用到我们的#运算符了,我们上面提到了其可以将我们后面的内容字符串化。

代码如下:

​​​​

我们看看其运行结果:

可以看到其完美的实现了我们需要的功能。

2、##运算符

##运算符又是一个完全陌生的符号,其作用是将其两边的符号合成一个符号,其运算宏定义从分离的文本片段创建标识符。##被称为记号粘合运算符。

比如我们现在有一个变量class115,那么我们可以通过粘合class和115来得到它

如下:

我们将其合并后,那么其此时就可以表示我们的变量了,那么我们使用printf将其打印出来,看看是不是10。

如下:

可以看到其可以顺利得到我们的这个变量名字。

下面我们再通过一个例子理解这个运算符:

我们前面使用函数写过一个比较两个数的大小,当我们比较的两个数是整数的时候,那我们就需要写一个整型数据求最大值的函数,当要求浮点数的时候,就需要写一个浮点数使用的函数,但是我们可以知道的是,这两个函数除了返回值类型,和参数的数据类型不一样罢了,其他的都是一样的。

如下:

下面我们通过宏来定义:

这个宏要实现的功能就是根据我们传入的数据类型,然后生成对应的求最大值的函数。

那么主要是解决对于数据类型如何与函数名相连了,那么刚刚好可以使用##进行粘合。

这些函数的名字的共同点如:数据类型_max。

如下:

上面就是我们创建的对于不同的数据类型都可以使用的求最大值的宏,我们根据用户传入的type来确定函数的名字和函数的返回值类型,然后\是一个换行符号。

这里我们使用了##运算符将type与_max连接起来,可以让用户传入的type可以完美的替换。

那么我们就可以通过这个宏来创建需要使用到的求最大值的函数:

运行结果:

二、预处理指令#under

#under指令的作用是移除一个#define的定义,其语法如下:

其中NAME就是我们要移除的#define定义的名字了,当我们需要使用这个名字重新定义一个宏的时候,那么我们就需要使用#under将其原来的定义移除,然后才可以使用这个名字进行定义一个新的宏。

如下:

上面我们首先使用#define将N定义为100,然后将其在屏幕上打印,然后我们使用#undef先将其原来的定义移除,然后再将N重新定义为hehe字符串,然后将其在屏幕上打印。

三、条件编译

条件编译,类似于我们的选择语句,不过这个过程是在预处理阶段进行的,它会根据我们的条件来决定是否对某些语句进行编译。

1、单分支条件翻译

单分支条件编译就是说我们只有一条分支需要进行判断。

其语法如下:

可以发现其和if语句类似,也是有个条件表达式决定其里面的内容编译不编译,不过其比if语句多了一个结束的标志。

不过条件表达式其要求的是常量表达式,下面我们使用其看看效果:

那么这个代码就啥都不会打印。因为常量表达式的值是为假的。

那么为什么后面只能为常量表达式呢?这是因为在这个阶段是预处理阶段,在这个阶段,变量还不存在,那么也就不能使用变量了,只能使用常量

2、多分支条件编译

多分支编译和单分支编译的原理大致相同,不过其就是多了几个选择这样。

其语法如下:

这个就是我们的多分支条件编译的语法,其用法和if -else的差不多,就是在第一个#if要是不满足,那么就看#elif的条件是否满足,要是满足则运行其代码,要是不满足,要是这两个都不满足,那么就会运行#else的内容,然后就结束。

如下:

上面的代码其是满足#elif的条件,那么我们看看其是否可以将hehe打印在屏幕上。

运行结果:

下面我们试试当前面两个的都不满足,其是否会运行#else的内容:

运行结果:

可以看到确实是如此。

那么细心的同学就会发现了,在我们的多分支条件编译中,会被编译的代码会比不被进行编译的代码颜色更深。

3、判断符号是否被定义

在编译一个程序的时候,如果我们要将一条语句,编译或者放弃编译,那么我们可以使用条件编译,比如调试性的代码,我们在调试完后,将其删去又会很浪费,但是保留又会很难看,影响我们代码的可读性,那么我们就可以使用条件编译,在编译的时候不编译这些调试性代码。

例如我们在开头使用了#define定义一个符号,如果我们没有注释或者删除这个符号,那么我们是可以编译里面的调试性代码的,反之就不可以编译。但是这个是不会影响代码的正常运行的。

我们先学习如何进行判断一个符号是否被定义:

1、使用#if define进行判断其语法如下:

2、使用#ifdef进行判断:

使用#ifdef的话,要判断的符号就不需要使用括号了。

下面我们使用其判读符号是否被定义:

我们首先定义一个GEBUG的符号,然后在main函数中创建一个变量a,然后我们使用#ifdef判断这个GEBUG符号是否被定义,要是被定义了就将这个变量打印出来。

如下:

运行结果:

我们注释掉它的定义后,那么就不会打印这个变量了:

4、判断符号是否没有被定义

其和上面的判断符号是否被定义差不多:

1、使用#if !defined:这个就是我们上面使用到的#if defined的前面加上一个非符号。

2、使用#ifndef:这个就是在上面的#ifdef加上一个n,表示no实际上就是表示否定。

四、头文件的包含

头文件的包含我们一直都有在使用,其本质上是拷贝,当我们包含一个头文件后,会直接将头文件的内容拷贝过来,下面我们具体学习一下头文件的包含。

1、库头文件的包含

库头文件里面包含了C语言的库函数和一些其他功能,我们只需要包含库的头文件就可以直接使用。

我们包含头文件的语法就是:

#include<头文件>

2、本地头文件的包含

本地头文件就是我们自己写的头文件,这种我们自己实现的头文件的话,我们包含其的语法如下:

#include "头文件"

3、如何避免重复包含头文件

我们在包含头文件中,我们可能会造成嵌套的包含,也就是可能会多次包含同一个头文件,那么这就会造成代码的冗余。

那么我们有没有什么办法解决这个问题呢?

我们可以使用条件编译指令:

就是使用#ifndef或者使用#if !defined这两个指令。

因为头文件其是就是简单的拷贝,那么我们可以在头文件中的开头写下面的代码:

或者使用预处理指令#pragma

这种方法比上面的还简答,我们可以在当前程序下创建一个头文件,我们可以发现头文件中自动包含了一条语句。

这样也可以达到我们想要的效果。

五、C语言完结

感想各位大佬们的阅读、点赞、收藏和评论,后面我们就会对数据结构的学习。

相关推荐
Scott9999HH6 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社7 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海7 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖8 小时前
JDK 26 新特性详解
java·开发语言
马优晨9 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区11 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大11 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai11 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜12 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa12 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio