【C语言终章】预处理详解(上)

news2024/11/24 23:03:52

【C语言终章】预处理详解(上)

  当你看到了这里时,首先要恭喜你!因为这里就是C语言的最后一站了,你的编程大能旅途也将从此站开始,为坚持不懈的你鼓个掌吧!

🥕个人主页:开敲🍉

🔥所属专栏:C语言🍓

🌼文章目录🌼

1. 预定义符号

2. #define定义常量

3. #denfine定义宏

4. 带用副作用的宏参数

5. 宏替换的规则

6. 宏和函数的对比

7. #和##

    7.1 #运算符

    7.2 ## 运算符

1. 预定义符号

  在C语言中的预处理阶段,会处理预定义符号,而C语言中设置了一些预定义符号,我们可以直接使用:

1   __FILE__    //当前文件的路径

2  __LINE__    //当前的行号

3  __DATE__    //当前程序编译瞬间的日期

4  __TIME__    //当前程序编译瞬间的时间

5  __STDC__   //如果编译器遵循ANSI C,其值为1,否则未定义

举个例子:

  在VS编译器中就不遵循ANSI C,因此报错。

2. #define定义常量

#define  name //名称 stuff //定义后的值  

例如:

1  #define  MAX   100    //定义MAX为100

2  #define reg  register   //为 register 这个关键字创建一个简短的名字

3  #define  do_forever  for(;;)   //为死循环创建一个名字

4  #define  CASE break;case      //在写case语句时自动把 break 加上

//如果define定义的语句过长,可以用续行符分成几行写

5  #define  DEBUG_PRINTF     printf("file :%s\tline:%d\t\

                                                     date:%s\ttime::%s\n",\

                                                     __FILE__,__LINE__,   \  //续行符

                                                     __DATE__,__TIME__)

问:在#define定义标识符时,后续需不需要加上;?

答:加上纯属多余,有时还会带来不好的后果

  例如:

这里我们在 100 加上了 ; ,在打印时直接报错了,这是为什么?

  因为MAX 是100;因此,在printf中它是这样的:

1  printf("%d\n",100;);

  在里面多了一个;这显然是错误的语法。

3. #denfine定义宏

  #define机制包括了一个规定,允许把参数直接原样替换到文本中,这种实现通常就称之为宏(macro)或者定义宏(define macro)。

  下面是宏的声明方式:

1  #define name(parament-list)  stuff   //parament-list   参数列表

  注:括号必须和name紧挨着,如果中间有空格,参数列表就会被视为stuff的一部分

举个例子:

1  #define  SQUARE(x)  x*x 

这个宏接收一个参数x,如果你在程序中写下 SQUARE(5),那么这个宏最终就会被预处理器给替换为  5*5

  注:这个宏存在一个问题,下面来看一段代码

  

根据我们以往的知识,这段代码最后输出的应当是 36,因为在函数中,这里传过去的值应当是6,然后计算的就是6*6。下面来看输出结果:

  可以看到,这里输出的居然是 11 ,这是为什么呢?原因就是上面说的原样替换

  像这样原样替换进去后,表达式就变成了 5+1*5+1,因此计算的结果就是11,那如何解决这个问题呢?很简单,只需要在定义时加上括号即可:

1  #define  SQUARE(x)  (x)*(x)

这里还有一个宏定义:

1  #define  ADD(x)  (x)+(x)

这个宏定义也是有着和上面一样的问题:

这里我们本意是想输出120,但这里却输出了66,还是原样替换的原因:

4. 带用副作用的宏参数

  当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。

  1  x+1;    //并没有改变x本身的值,不带有副作用

  2  x++;   //改变了x本身的值,带有副作用

  下面来看一段代码:

  思考一下上面的代码输出的是什么。

  

可以看到,最后输出时,a和b的值都被改变了,这也是上面所说的原样替换:

  

5. 宏替换的规则

  在程序中扩展#define定义符号和宏时,需要涉及几个步骤:

①  在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,他们首先被替换,例如:

②  替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。

③  再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,那么就重复上述过程

  例如:

  注意:

①  宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏,不能,因为宏无法递归。

②  当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索,例如:

6. 宏和函数的对比

  宏通常被用于执行简单的运算,比如在两个数中找出较大的数就可以用下面的宏:

1  #define  MAX(a,b)  (a)>(b)?(a):(b)

  那为什么不用函数来完成这个任务呢?

有两点原因:

①  用于调用函数和从函数返回的代码可能比实际执行这个小型计算机工作所需要的时间更多。所以宏在执行简单运算时在规模和速度方面都要优于函数。

 ②  更为重要的是函数的参数不能以类型的形式传递,所以函数只适合在类型合适的表达式上用。但是宏可以,因为宏所做到的是原样替换,因此在传参是宏并不会在乎你传的是什么,它会直接把你传的参数给替换进宏中,例如:

  前面说了和函数相比宏的优势,接下来讲讲和函数相比宏的劣势:

①  每次使用宏的时候,一份宏定义的代码将插入到程序中。除非定义的宏比较短,否则可能大幅增加程序的长度。

②  由于宏只是一条代码指令,一次性就完成了好几条甚至更多的代码指令,因此,宏是没法进行调试的。

③  宏由于类型无关,因此也就不够严谨

④  宏可能会带来运算符优先级的问题(比如 5.宏替换规则 中的),导致程序的可能会出错。

7. #和##
    7.1 #运算符

  #运算符将宏的一个参数转换为字符串字面量注:它仅允许出现在带参数的宏的替换列表中。

  #运算符所执行的操作可以理解为 "字符串化"。

  下面来看一段代码:

当我们想用宏来实现这一段代码时应该如何做到呢?

我们可能会想到这种方法:

1  #define PRINT(a)  printf("the value of a is %d\n",a)

显然,上面这种方法是行不通的,因为预处理器不会扫描字符串常量中的内容,因此,printf中的a并不会被替换,那么这时候就需要我们用到#运算符了:

这里使用#运算符将a字符串化,变为字符串字面量,这也预处理器就可以扫描a并将其替换。

    7.2 ## 运算符

  ##运算符可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。##被称为记号粘合。

  注:这样的连接必须产生一个合法的标识符。否则其结果是未定义的。

  这里我们想想,在我们想要求两个数中的较大值时,使用函数的话,比较不同类型的数就得实现不同的函数,比如:

  显然,这样写起来太麻烦了,现在我们学习了宏定义之后,可以试着写一下宏:

  在实际的开发过程中##使用的很少,因此很难举例出形象贴切的例子。

                                                创作不易,点个赞呗,蟹蟹啦~

    

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Effective Web】文件上传

文章目录 前言一、选择本地文件1.设计一个上传文件按钮2.FileReader读取文件内容 二、使用拖拽方式1.设计一个拖拽容器2.拖拽文件的相关事件回调 三、使用粘贴方式1.设计一个粘贴容器2.paste事件回调 四、总结 前言 前端无法像app一样直接操作本地文件,对本地文件的…

「Android高级工程师」BAT大厂面试基础题集合-下-Github标星6-5K

C、 com.android.provider.contact D、 com.android.provider.contacts 11.下面关于ContentProvider描述错误的是()。 A、 ContentProvider可以暴露数据 B、 ContentProvider用于实现跨程序共享数据 C、 ContentProvider不是四大组件 D、 ContentP…

【Java数据结构】关于栈的操作出栈,压栈,中缀表达式,后缀表达式,逆波兰表达式详解

🔥个人主页:努力学编程’ 🔥内容管理:java数据结构 上一篇文章我们讲过了java数据结构的链表,对于链表我们使用了它的一些基本操作,完成了扑克牌小游戏的操作,如果你感兴趣的话,点…

Optimizer神经网络中各种优化器介绍

1. SGD 1.1 batch-GD 每次更新使用全部的样本,注意会对所有的样本取均值,这样每次更新的速度慢。计算量大。 1.2 SGD 每次随机取一个样本。这样更新速度更快。SGD算法在于每次只去拟合一个训练样本,这使得在梯度下降过程中不需去用所有训…

SpringBoot 整合Redis第1篇

SpringBoot是一个开发框架,Redis是一个高性能的键值存储数据库, 常用于缓存、会话管理、消息队列等应用场景。 定义 Redis是什么? 它是一个存储层级, 在实际项目中,位于关系数据库之上, 类似Android分为5…

(C语言)fread与fwrite详解

1. fwrite函数详解 头文件&#xff1a;stdio.h 函数有4个参数&#xff0c;只适用于文件输出流 作用&#xff1b;将从ptr中拿count个大小为size字节的数据以二进制的方式写到文件流中。返回写入成功的数目。 演示 #include <stdio.h> int main() {FILE* pf fopen(&qu…

相册清理大师-手机重复照片整理、垃圾清理软件

相册清理大师是一款超级简单实用的照片视频整理工具。通过便捷的操作手势&#xff0c;帮助你极速整理相册中的照片和视频、释放手机存储空间。 【功能简介】 向上滑动&#xff1a;删除不要的照片 向左滑动&#xff1a;切换下一张照片 向右滑动&#xff1a;返回上一张照片 整理分…

【shell】select in实现终端交互场景

文章目录 序言1. select in语句及其语法2. select in和case语句相结合3. 执行界面示例 序言 shell脚本实现简单的终端交互功能&#xff0c;根据用户不同输入执行不同功能脚本 1. select in语句及其语法 select in是shell独有的一种循环&#xff0c;非常适合终端交互场景 该语…

链表的极致——带头双向循环链表

​ 文章目录 双向带头循环链表简介&#xff1a;双向&#xff1a;带头&#xff1a;特点&#xff1a;链表带头节点的好处&#xff1a; 循环&#xff1a;特点&#xff1a;循环的好处&#xff1a; 双向带头循环链表的接口函数实现准备工作&#xff1a; 初始化链表&#xff08;头结…

C++:数据类型—布尔(12)

布尔类型代表就是真和假&#xff08;bool&#xff09; 真就是1&#xff08;true&#xff09; 假就是0&#xff08;false&#xff09; 也可以任务非0即为真 bool 直占用1个字节大小 语法&#xff1a;bool 变量名 (true | false&#xff09; 提示&#xff1a;bool在后期判断也是…

深度学习pytorch——经典卷积网络之ResNet(持续更新)

错误率前五的神经网络&#xff08;图-1&#xff09;&#xff1a; 图-1 可以很直观的看到&#xff0c;随着层数的增加Error也在逐渐降低&#xff0c;因此深度是非常重要的&#xff0c;但是学习更好的网络模型和堆叠层数一样简单吗&#xff1f;通过实现表明&#xff08;图-2&…

《自动机理论、语言和计算导论》阅读笔记:p49-p67

《自动机理论、语言和计算导论》学习第4天&#xff0c;p49-p67总结&#xff0c;总计19页。 一、技术总结 1.Deterministic Finite Automata(DFA) vs Nondeterministic Finite Automata(NFA) (1)DFA定义 (2)NFA定义 A “nonedeterministic” finite automata has the power t…

python之绘制曲线

以同一种型号的钻头&#xff0c;钻21种类型的板材&#xff0c;每种板材使用3根钻头&#xff0c;分别在钻第一个孔、2001孔、4001孔和6001孔前测量钻头外径&#xff0c;收集数据。 1、测试方法 采用激光钻径分选机测量微钻钻径以评估微钻外径磨损&#xff0c;测量从钻尖起始间…

C语言-文件

目录 1.什么是文件&#xff1f;1.1 程序文件1.2 数据文件 2.二进制文件和文本文件&#xff1f;3.文件的打开和关闭4.文件的顺序读写5.文件的随机读写5.1 fseek5.2 ftell5.3 rewind 6.文件读取结束的判定7.文件缓冲区 1.什么是文件&#xff1f; 磁盘上的文件就是文件 一般包含两…

使用pytorch构建带梯度惩罚的Wasserstein GAN(WGAN-GP)网络模型

本文为此系列的第三篇WGAN-GP&#xff0c;上一篇为DCGAN。文中仍然不会过多详细的讲解之前写过的&#xff0c;只会写WGAN-GP相对于之前版本的改进点&#xff0c;若有不懂的可以重点看第一篇比较详细。 原理 具有梯度惩罚的 Wasserstein GAN (WGAN-GP)可以解决 GAN 的一些稳定性…

caffe源码编译安装

一、前置准备 (1)vs2015 目前不要想着2019这些工具了,成功率太低了,就老老实实用vs2015吧 解决“VS2015安装包丢失或损坏“问题_vs2015跳过包会影响使用吗-CSDN博客 注意在安装vs2015过程中老是出现这个问题,其实就是缺少两个证书,安装完后就可以正常安装vs2015了,注意…

大数据面试专题 -- kafka

1、什么是消息队列&#xff1f; 是一个用于存放数据的组件&#xff0c;用于系统之间或者是模块之间的消息传递。 2、消息队列的应用场景&#xff1f; 主要是用于模块之间的解耦合、异步处理、日志处理、流量削峰 3、什么是kafka&#xff1f; kafka是一种基于订阅发布模式的…

AE——重构数字(Pytorch+mnist)

1、简介 AE&#xff08;自编码器&#xff09;由编码器和解码器组成&#xff0c;编码器将输入数据映射到潜在空间&#xff0c;解码器将潜在表示映射回原始输入空间。AE的训练目标通常是最小化重构误差&#xff0c;即尽可能地重构输入数据&#xff0c;使得解码器输出与原始输入尽…

什么是nginx正向代理和反向代理?

什么是代理&#xff1f; 代理(Proxy), 简单理解就是自己做不了的事情或实现不了的功能&#xff0c;委托别人去做。 什么是正向代理&#xff1f; 在nginx中&#xff0c;正向代理指委托者是客户端&#xff0c;即被代理的对象是客户端 在这幅图中&#xff0c;由于左边内网中…

如何解决kafka rebalance导致的暂时性不能消费数据问题

文章目录 背景思考答案排它故障转移共享 背景 之前在review同组其它业务的时候&#xff0c;发现竟然把kafka去掉了&#xff0c;问了下原因&#xff0c;有一个单独的服务&#xff0c;我们可以把它称为agent&#xff0c;就是这个服务是动态扩缩容的&#xff0c;会采集一些指标&a…