C语言勘破之路-最终篇 —— 预处理(上)

在这里插入图片描述

在这里插入图片描述

人无完人,持之以恒,方能见真我!!!
共同进步!!

文章目录

  • 一、预定义符号
  • 二、#define定义常量
  • 三.、#define定义宏
  • 四、带有副作用的宏参数
  • 五、宏替换的规则
  • 六、宏和函数的对比
    • 1.宏的优势
    • 2.函数的优势
    • 3.宏和函数的命名约定

一、预定义符号

上一篇讲到编译和连接,这一篇就详细讲讲预处理(预编译)

C语⾔设置了⼀些预定义符号,可以直接使⽤,预定义符号也是在预处理期间处理的,如下:

_ _FILE_ _ 
__LINE__ 
__DATE__ 
__TIME__ 
__STDC__ 

我们需要注意的是,使用这些预定义符号的时候,下面的两个短下划不能少,并且两个短下划线之间是没有间隙的,第一个只是方便我们观察,实际是没有空格的

接下来我们来再详细介绍一下它们:

  1. FILE代表当前进行编译的源文件,在打印时,需要使用占位符%s,它不仅会打印文件名,还会打印文件的完整路径
  1. LINE代表出现了这个预定义符号的行号,比如这个预定义符号出现在第6行时,那么它就代表6,所以需要使用%d进行打印
  1. DATE代表文件被编译时的日期,打印时需要使用占位符%s
  1. TIME代表文件被编译时的具体时间,具体到时分秒,打印时也是使用占位符%s
  1. STDC就与编译文件的编译器有关了,如果编译当前文件的编译器完全遵守了ANSI C标准,那么它将会被定义,并且值为1,打印时需要使用%d,如果该编译器不完全遵守ANSI C标准,那么STDC这个预定义符号就没有被定义过,如果使用它就会报错

接着我们就来使用一下这几个预定义符号,首先我们来使用前4个预定义符号,来打印我们源文件在编译时的各种信息

#include <stdio.h>int main()
{printf("FILE: %s\n", __FILE__);printf("LINE: %d\n", __LINE__);printf("DATE: %s\n", __DATE__);printf("TIME: %s\n", __TIME__);return 0;
}

运行结果

在这里插入图片描述

接着我们就可以使用STDC这个预定义符号,来判断我们的编译器是否完全遵循ANSI C

可以看到VS2022在运行时报错了,不认识这个标识符,说明我们的VS2022并没有严格遵守ANSI C标准

在这里插入图片描述

二、#define定义常量

#define定义常量的基本语法如下:

#define name stuff

其中的name就是我们定义的常量的名称,stuff就是我们定义的常量的值,可以是整型,可以是字符串,也可以是字符等等

接着我们就使用#define来定义各种类型的常量,我们要注意的一点是,在取名时我们的常量名最好全部大写,这是我们编程的一种习惯,如下:

#include <stdio.h>#define MAX 100
#define STR "I am Sam!"
#define CH 'x'int main()
{printf("MAX: %d\n",MAX);printf("STR: %s\n",STR);printf("CH : %c\n", CH);return 0;
}

在这里插入图片描述

需要注意的是,我们再define 的时候,最好不要定义分号

#define MAX 100;int a = MAX;

替换一下,我们就能发现问题所在

int a = 100;;

所以,我们要尽量避免上面这种情况,因为这样使用会产生很多不确定性

三.、#define定义宏

#define 机制包括了⼀个规定,允许把参数替换到⽂本中,这种实现通常称为宏(macro)或定义宏(definemacro),下面是宏的声明方式:

#define name( parament-list ) stuff

其中的parament-list 是⼀个由逗号隔开的符号表,它们可能出现在stuff中,要注意的是:参数列表的左括号必须与name紧邻,如果两者之间有任何空⽩存在,参数列表就会被解释为stuff的⼀部分

是不是有点难懂,我们可以看如下的例子:

#define SQUARE( x )  x * x

它的形式有点类似于函数,前面就相当于函数名,括号中就是宏的参数,后面是这个宏的计算方式,比如使用SQUARE(5),那么预处理后,就会把这条语句转化成5*5

其中SQUARE和第一个小括号要紧紧贴在一起,如果两者之间有任何空⽩存在,那么(x)就会成为后面的一部分,就会出错

那么我们上面写的这个宏是否就完全正确了呢?其实它还存在一个问题,比如我们来看一个例子:

#include <stdio.h>#define SQUARE( x ) x * xint main()
{int a = 5;printf("%d\n", SQUARE(a + 1));return 0;
}

但是要注意带入的元素的运算优先级

在这里插入图片描述

其中的x会直接被a+1替换,那么SQUARE(x)经过替换过后应该是如下的样子:

a + 1 * a + 1
//带入a=5
5 + 1 * 5 + 1

但是只要我们给定义的元素加上括号,就能减少错误发生的概率

在这里插入图片描述

那么这样是否就一定不会出错了呢?这里就不卖关子了,这样还是不能确保得到我们预期的结果,为什么呢?

#include <stdio.h>#define DOUBLE( x ) (x) + (x)int main()
{int a = 5;printf("%d\n", 10 * DOUBLE(a));return 0;
}

按照我们的预期,宏DOUBLE会帮我们计算出一个数的2倍,那么这里5的2倍是10,乘以10过后就变成了100,那么我们来看最后的结果是否是100

在这里插入图片描述

10 * (a) + (a)
//将a替换成5之后
10 * (5) + (5)

这个时候就可以看出来,由于*的优先级更高,所以10和前面那个5结合变成了50,然后+5变成了55,这就是55的由来,所以我们可以看出,光给每个参数加上()还不够,我们还最好把整个式子括起来,表示它们是一个整体

#define DOUBLE( x ) ((x) + (x))

在这里插入图片描述

可以看到最后结果就正确了,所以总结一下,在我们使用宏定义的时候,我们要使用()将每个参数括起来,保证每个参数是一个整体,最后我们还要使用()将整个式子括起来,保证整个式子是一个整体

四、带有副作用的宏参数

宏参数还有副作用,是不是基本上没有听过这种说法,为什么会这么说呢?我们一起来学习一下:

带有副作用的宏参数就是:当宏参数在宏的定义中出现超过⼀次的时候,如果参数带有副作⽤,那么你在使⽤这个宏的时候就可能出现危险,导致不可预测的后果,其中副作⽤就是表达式求值的时候出现的永久性效果

我们举个栗子来看一下:

//不带副作⽤
x+1;
//带有副作⽤
x++;

乍一看这两者不是一样的吗?但其实并不一样,因为x++对x造成了永久性的效果,就是对x自增了一个1,而x+1这个表达式对x并没有影响

接着我们来看一个例子来更好的理解,我们来定义一个宏,它的功能就是帮我们找到两个数中的最大数:

#include <stdio.h>#define MAX(x,y) ( (x) > (y) ? (x) : (y) ) int main()
{int a = 5;int b = 2;int ret = MAX(a++, b++);printf("a = %d b = %d ret = %d\n", a, b, ret);return 0;
}  

这个例子的运行结果是什么呢?我们预期的结果是a变成6,b变成3,ret则是5,因为传参的时候使用的是后置++,所以是先使用a和b的值,也就是把5和2作为参数传过去后,然后a和b再++,所以a变成了6,b变成了3,ret还是5

结果如下:

在这里插入图片描述

这里的结果和我们预期还是不一样,本质还是由于在预处理阶段,会将宏直接替换过来

( (a++) > (b++) ? (a++) : (b++) ) 

在执行这条语句时,首先会执行(a++) > (b++),此时这里是后置++,所以a和b先使用再自增1,由于a是5,b是2,a>b成立了,然后对a和b进行自增1,a就变成了6,b就变成了3

由于(a++) > (b++)的结果为真,所以最后整个三目表达式返回的就是a++的结果,由于这里还是后置++,所以返回的就是6,然后对a自增1变成7,所以最后ret的值就是6,a的值为7,b的值为3

所以我们在使用宏的时候最好不要使用带副作用的宏参数,也就是使用后会对原本的参数造成永久性效果的表达式,例如++和- -操作

五、宏替换的规则

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

  1. 在调⽤宏时,⾸先对参数进⾏检查,看看是否包含任何由#define定义的符号。如果是,它们⾸先被替换
  2. 替换⽂本随后被插⼊到程序中原来⽂本的位置,不做任何更改,而对于宏,参数名被它们的值所替换
  3. 最后,再次对结果⽂件进⾏扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程

注意

  1. 宏参数和#define定义中可以出现其他#define定义的符号,比如先使用#define定义一个常量N,值为100,那么这个N就可以在另一个#define中出现,但是对于宏,不能出现递归
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索

六、宏和函数的对比

1.宏的优势

宏通常被应⽤于执⾏简单的运算,而函数则可以应用于较为复杂的场面,⽐如在两个数中找出较⼤的⼀个时,写成下⾯的宏,更有优势⼀些

#define MAX(x,y) ( (x) > (y) ? (x) : (y) ) 

那为什么不⽤函数来完成这个任务?原因有2点:

  1. ⽤于调⽤函数和从函数返回的代码可能⽐实际执⾏这个⼩型计算⼯作所需要的时间更多,因为函数还要开辟自己的栈帧,进行返回等等操作,所以宏⽐函数在程序的规模和速度⽅⾯更胜⼀筹
  1. 更为重要的是函数的参数必须声明为特定的类型,所以函数只能在类型合适的表达式上使⽤,反之这个宏怎可以适⽤于整形、⻓整型、浮点型等可以⽤于>来比较的类型,宏的参数是类型⽆关的,比如上面我们定义的MAX宏,不仅可以比较整型,同时也可以比较浮点型和长整型等等,而一个函数只能比较单个数据类型

2.函数的优势

对于宏来说,函数也有它的优势,它们没有一定的哪一个好,只有哪一个更适合我们的需求,那么对比宏,函数的优势如下:

  1. 每次使⽤宏的时候,⼀份宏定义的代码将插⼊到程序中,除⾮宏⽐较短,否则可能⼤幅度增加程序的⻓度
  2. 宏是没法调试的,而函数可以一步一步调试,查看bug出现的原因
  3. 宏由于类型⽆关,也就不够严谨,这在上面成为了它的优势,但是在某些场景导致它的不够严谨,这个时候就要使用函数
  4. 宏可能会带来运算符优先级的问题,导致程序容易出错,比如忘记对参数加上(),或者忘了给整个式子加上()都可能出现预期以外的结果

3.宏和函数的命名约定

⼀般来讲函数的宏的使⽤语法很相似,并且语⾔本⾝没法帮我们区分⼆者,所以我们平时就通过命名来简单区分它们,接下来我们来看看它们的命名约定:

  1. 宏名全部大写
  2. 函数名不要全部大写,一般是多个单词中,每个单词的首字母大写

今天的最终篇上篇就分享到这里,下一篇就是C语言的最后一篇了,C语言的勘破之路马上就要被我们走完了,希望大家多多坚持,马上就要开启我们的数据结构之旅了

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

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

相关文章

学习threejs,THREE.RingGeometry 二维平面圆环几何体

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.RingGeometry 圆环几…

Win11系统下Oracle11g数据库下载与安装使用教程

文章目录 一、Oracle下载与安装1.1 解压安装包1.2 开始安装Oracle11g1.2.1 用户 1.3 测试数据库是否配置成功1.4 了解一下 Oracle相关服务1.5 了解Oracle体系结构 二、使用工具连接数据库2.1 PL/ SQL 连接本地oracle 三、PL/ SQL远程访问数据库3.1 可能踩坑问题&#xff08;TNS…

数据结构(Java版)第六期:LinkedList与链表(一)

目录 一、链表 1.1. 链表的概念及结构 1.2. 链表的实现 专栏&#xff1a;数据结构(Java版) 个人主页&#xff1a;手握风云 一、链表 1.1. 链表的概念及结构 链表是⼀种物理存储结构上⾮连续存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的引⽤链接次序实现的。与火车…

从零开始C++棋牌游戏开发之第三篇:游戏的界面布局设计

在游戏开发的旅途中&#xff0c;界面布局设计是一个充满创意和挑战的环节。对于棋牌类游戏而言&#xff0c;界面不仅仅是功能的载体&#xff0c;更是玩家与游戏互动的桥梁。一个清晰、直观且美观的界面可以显著提升游戏的用户体验。 在这篇文章中&#xff0c;我们将从功能需求…

计算机基础知识——数据结构与算法(五)(山东省大数据职称考试)

大数据分析应用-初级 第一部分 基础知识 一、大数据法律法规、政策文件、相关标准 二、计算机基础知识 三、信息化基础知识 四、密码学 五、大数据安全 六、数据库系统 七、数据仓库. 第二部分 专业知识 一、大数据技术与应用 二、大数据分析模型 三、数据科学 数据结构与算法…

数据库管理-第275期 Oracle 23ai:画了两张架构图(20241225)

数据库管理275期 2024-12-25 数据库管理-第275期 Oracle 23ai&#xff1a;画了两张架构图&#xff08;20241225&#xff09;1 系统管理分片2 用户定义分片总结 数据库管理-第275期 Oracle 23ai&#xff1a;画了两张架构图&#xff08;20241225&#xff09; 作者&#xff1a;胖…

增强路由器 路由器升级宽带速度

由器中DNS设置 DNS&#xff08;域名系统&#xff09;是什么&#xff1f; DNS将域名和IP地址相互映射的一个分布式数据库&#xff0c;能够使人更方便地访问互联网。DNS使用UDP端口53。 上网前提是&#xff1a;配置 IPv4地址、子网掩码 、网关、DNS 正确才能够上网 DNS填写规…

stm32制作CAN适配器5--WinUsb上位机编写

上次我们要stm32制作了一个基于winusb有canfd适配器&#xff0c;今天我们来制作一个上位机程序来进行报文收发。 上位机还是用以前写好的&#xff0c;只是更改下dll文件。 项目链接器&#xff0c;输入&#xff0c;附加依赖项中增加winusb.lib winusb初始化&#xff1a;#incl…

谷歌浏览器 Chrome 提示:此扩展程序可能很快将不再受支持

问题现象 在Chrome 高版本上的扩展管理页面&#xff08;地址栏输入chrome://extensions/或者从界面进入&#xff09;&#xff1a; &#xff0c; 可以查看到扩展的情况。 问题现象大致如图: 问题原因 出现此问题的根本原因在于&#xff1a;谷歌浏览器本身的扩展机制发生了…

Vue.js组件(6):echarts组件

1 前言 本章主要对常用的echars图表展示进行基本的组件封装。使用该组件前需要在项目中引入echarts。官网&#xff1a;Apache ECharts npm install echarts --save 2 图表组件 2.1 折线图组件 组件属性&#xff1a;chartId&#xff0c;指定图表挂载div的id&#xff0c;注意不…

MySQL用表组织数据

用表组织数据 文章目录 用表组织数据一.四种完整性约束二.数值类型2-1三.数值类型2-2四.字符串.日期类型五.设置1.设置主键2.设置标识列3.设置非空4.设置默认值 六.主外键建立后注意事项 一.四种完整性约束 1.域完整性 列 域完整性约束方法:限制数据类型,检查约束,外键约束,默…

面试经典问题 —— 最大/小前K个数问题(top - K)问题

目录 常见思路更优的解法&#xff08;面试官喜欢的&#xff09; 常见思路 要选出最小的前K个数首先我们会想到排排升序建大堆&#xff0c;排降序建小堆 一个直观的想法是使用&#xff08;小根堆&#xff09;&#xff0c;起始将所有元素放入堆中&#xff0c;然后再从堆中取出k 个…

外包干了27天,技术退步明显。。。。。

时光荏苒&#xff0c;转眼我已是一个拥有近四年功能测试经验的大专生。20年&#xff0c;我满怀激情地通过校招进入湖南某知名软件公司&#xff0c;期待在这里开启我的职业生涯。然而&#xff0c;长时间的舒适环境让我渐渐失去了前进的动力&#xff0c;技术停滞不前&#xff0c;…

从自动驾驶到具身智能漫谈

0. 简介 从作者的眼光来看自动驾驶和具身智能已经是越来越接近了。无论是技术栈以及实现的最终目的。其实都是希望人在环内。这个是古月直播的文字相关的大致梳理。主要会展开聊一聊自动驾驶的变迁以及作为自动驾驶的从业人员要着重关注的一些技术点 1. 自动驾驶的变迁 在自…

Excel粘贴复制不完整的原因以及解决方法

在数据处理和分析的过程中&#xff0c;Excel无疑是不可或缺的工具。然而&#xff0c;在使用Excel进行复制粘贴操作时&#xff0c;有时会遇到粘贴不完整的情况&#xff0c;这可能会让人感到困惑和烦恼。本文将深入探讨Excel粘贴复制不完整的原因、提供解决方案&#xff0c;并给出…

云原生之docker详解

目录 1.云原生概念 1.1 云原生定义 1.2 云原生元素 1.2.1 微服务 1.2.2 DevOps 1.2.3 持续交付 1.2.4 容器化 2. Docker 2.1 Docker概述 2.1.1 Docker 定义 2.1.2 Docker应用场景 2.1.3 Docker的架构 2.2 Docker命令 2.2.1 docker进程相关命令 2.2.2 docker镜像…

数仓开发那些事(8)

程序员圣经 为什么刚刚能运行&#xff0c;现在就不行 为什么刚刚不运行&#xff0c;现在就可以 为什么他的可以跑&#xff0c;我的不能跑 为什么我的可以跑&#xff0c;他的就不行 为什么这台电脑能&#xff0c;那台就不行 为什么这台电脑不行&#xff0c;那台就行 神州员工&a…

在UE5中调用ImGui图形界面库

ImGui是一个小巧灵活、简洁美观的图形界面库 首先我们直接参考Github https://github.com/SLSNe/Unreal5-ImGui 把项目下载下来后 打开项目目录或者引擎目录 项目根目录/Plugins/ImGui/ 或 UE5引擎根目录/Engine/Plugins/ 如果没有Plugins文件夹就新建一个 把项目放里面…

华为管理变革之道:奋斗文化与活力

目录 企业文化是什么&#xff1f; 为什么活下去是华为的文化&#xff1f; 活下来&#xff0c;是华为公司的最低纲领&#xff0c;也是华为公司的最高纲领&#xff01; 资源终会枯竭&#xff0c;唯有文化才能生生不息 企业文化之一&#xff1a;以客户为中心 企业文化之二&a…

JZ31 栈的压入、弹出序列

题目来源&#xff1a;栈的压入、弹出序列_牛客题霸_牛客网 题目&#xff1a;如下 输入两个整数序列&#xff0c;第一个序列表示栈的压入顺序&#xff0c;请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序&#xf…