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

【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 ## 运算符

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

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

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

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

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

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

    

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

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

相关文章

代码随想录算法训练营三刷 day38 | 动态规划之 509. 斐波那契数 70. 爬楼梯 746. 使用最小花费爬楼梯

三刷day38 509. 斐波那契数1 确定dp数组以及下标的含义2 确定递推公式3 dp数组如何初始化4 确定遍历顺序5 举例推导dp数组 70. 爬楼梯1 确定dp数组以及下标的含义2 确定递推公式3 dp数组如何初始化4 确定遍历顺序5 举例推导dp数组 746. 使用最小花费爬楼梯1 确定dp数组以及下标…

经验分享:开源知识库才是企业低成本搭建的最佳选择!

身为企业所有者的你,是否为建设企业的知识库而头疼?想要一个功能全面而又简单易用的知识库,但又担心成本过高?那我今天要分享的内容,可能会给你带来一些启示。那便是:开源知识库便是你企业低成本搭建的最佳…

Stable Diffusion 模型下载:epiCPhotoGasm(真实、照片)

本文收录于《AI绘画从入门到精通》专栏,专栏总目录:点这里,订阅后可阅读专栏内所有文章。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八 下载地址 模型介绍 该模型对照片是什么有很高的了解,所以…

Arcgis获取乡镇矢量

现有全中国乡镇矢量边界(2023年),如何获取其中的自己所需的子区域(一个小镇)呢? 可以先去查一下自己的镇代码,我查的是东马圈镇代码 打开分析工具-提取分析-筛选 刚刚记下了FID 验证一下&am…

武汉星起航:引领跨境电商新纪元,助力卖家实现全球业务飞跃

在全球化的时代背景下,越来越多的中国卖家正选择跨境电商作为他们拓展国际市场的重要途径。然而,对于许多新手卖家而言,如何进入海外市场、如何运营店铺、如何推广产品,都是一道道难以逾越的难题。在这个关键时刻,武汉…

词令用户常说的:词令关键词口令直达与词令关键词直达口令有什么区别?

词令是一种通过输入特定关键词令,直达该词令关联的网站、页面、程序、应用、功能、服务或目标的工具。词令可以帮助用户更快速、更便捷的直达目标的提效软件。而词令用户常说的:词令关键词口令直达与词令关键词直达口令有什么区别? 词令关键词…

【多模态对比学习】我遇到的坑

对比学习踩坑记录 本文是对过去几个月来利用对比学习的思想来优化多模态学习任务的思路的总结,主要包含以下几个方面:为什么要用对比学习、跨模态中对比学习怎么用、对比的过程中负样本是不是越多越好、要不要推远所有的负样本、样本之间的语义碰撞如何缓…

低噪声、轨至轨运算放大器芯片—— D721、D722、D724,适合用于音频领域

应用领域 D721、D722、D724是我们推荐的三款低噪声、轨至轨运算放大器芯片,其中D721为单运放,D722为双运放,D724为四运放。适合用于音频领域、传感器等的信号放大处理,比如K歌宝、音响、测距、滤波器、AD转换器前级信号处理等等。…

【Go】二、Go的基本数据类型

文章目录 0、进制1、变量的声明2、数据类型3、整型4、浮点型5、字符类型6、布尔类型7、字符串类型8、基本数据类型的默认值9、类型转换10、基本类型转String11、String转其他类型 0、进制 N进制,逢N进一 1、变量的声明 //声明 赋值 var age int 18//声明、赋值、…

基于单片机20v数字电压表仿真系统设计

**单片机设计介绍,基于单片机20v数字电压表仿真系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机20V数字电压表仿真系统设计是一个结合了硬件与软件设计的综合性项目。以下是对该设计概要的详细描述…

使用 CSS 实现多立方体悬停颜色效果实现

使用 CSS 实现多立方体悬停效果实现 效果展示 CSS 知识点 filter 属性的 hue-rotate 值运用使用 CSS 实现立方体 场景布局分析 从效果图可以看出,要实现 3*3 的立方体集合,我们需要考虑一下怎么安排小立方体的布局。我这里的做法是使用span实现单个小…

账号微服务短信验证码发送工具单元测试

账号微服务短信验证码发送工具单元测试 注意sms的 app-code #----------sms短信配置-------------- sms:app-code: dd7829bedfaf4373875aa91abba82523template-id: JM1000372package net.xdclass.config;import org.springframework.context.annotation.Bean; import org.spri…

klmanga 章节链接获取

两年前还是可以直接爬的,现在章节不显示了 直接爬取下来的源码章节显示loading,在源码中可以看到是通过js加载出来的,搜索loadChapterDate dataFile atob(FR),这是将FR的值用base64解码赋予dataFile 根据这段var paramsnew …

原生JS/JQuery封装请求工具类

原生JS的用途 原生 JavaScript 是一种用于开发网络应用程序的编程语言。它可以用于网页开发、服务器端开发和移动应用开发等多个方面。以下是原生 JavaScript 的一些主要用途: 网页开发:原生 JavaScript 可用于处理 DOM(文档对象模型&#x…

指针知识大礼包,让你的编程之路更顺畅(四)

1. 回调函数是什么? 2. qsort使⽤举例 3. qsort函数的模拟实现 4. sizeof和strlen的对⽐ 5. 数组和指针笔试题解析 6. 指针运算笔试题解析 正文开始: 1. 回调函数是什么? 回调函数就是⼀个通过 函数指针 调⽤的函数。 如果你把函数的…

【JavaSE】一维数组和二维数组详解

欢迎关注个人主页:逸狼 创造不易,可以点点赞吗~ 如有错误,欢迎指出~ 目录 一维数组 基本语法 初始化 遍历和打印 数组是引用型变量 基本类型变量与引用类型变量的区别 null 数组传参和返回 总结 二维数组 基本语法 初始化 遍历和打印 一维数组…

动态表情包怎么制作?超简单的gif动画制作方法

动态gif表情包非常的收大众欢迎,小编也不例外。日常聊天斗图中都少不了它们的存在,gif表情包能够调节聊天的氛围。那么,这种gif表情包是怎么做的呢?下面就跟着小编一起来看看制作gif动画图片的方法,不需要下载任何软件…

基于GA遗传优化的离散交通网络双层规划模型设计matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于GA遗传优化的离散交通网络双层规划模型设计.优化输出路段1和路段2的收费情况收敛过程。 2.测试软件版本以及运行结果展示 MATLAB2022a版本运行 3.核心程序…

独家原创!TCN-BiGRU-Attention一键实现西储大学故障诊断与讲解!附带处理好的Excel故障诊断数据集!

声明:文章是从本人公众号中复制而来,因此,想最新最快了解各类智能优化算法及其改进的朋友,可关注我的公众号:强盛机器学习,不定期会有很多免费代码分享~ 目录 数据介绍与故障诊断讲解 1.数据预处理 2.特…

【Python】基础语法(1)

目 录 一.常量和表达式二.变量和类型变量是什么变量的语法(1) 定义变量(2) 使用变量 变量的类型(1) 整数(2) 浮点数(小数)(3) 字符串(4) 布尔(5) 其他 为什么要有这么多类型?动态类型特性 三.注释注释是什么注释的语法(1) 注释行(2) 文档字符串 注释的规范 四.输入输出和用户交…