【C语言】预处理详解(上)
文章目录
- 前言
- 1. 预定义符号
- 2. #define 定义常量
- 3. #define定义宏
- 4. 带有副作用的宏参数
- 5. 宏替换的规则
前言
在讲解编译和链接的知识点中,我提到过翻译环境中主要由编译和链接两大部分所组成。
其中,编译又包括了预处理、编译和汇编。当时,我只是粗略的讲解预处理的过程,那么本文将会带着大家去领略预处理的各项操作。还有一些预处理的奇葩操作。
1. 预定义符号
C语言设置了一些预定义符号,可以直接使用,预定义符号也是在预处理阶段就被直接替换掉了。
预处理符号:
__FILE__ //意思:进行编译的源文件
__LINE__ //意思:显示该代码语句所在的行数
__DATE__ //意思:文件被编译的日期
__TIME__ //意思:文件被编译的时间
__STDC__ //意思:如果该C编译器完全遵顼ANSI C的标准,则其值为0。否则就是非定义。
演示案例:
2. #define 定义常量
基本语法:
#define name stuff
举个例子:
//#define 定义常量
#define MAX 10000
#define reg register //为register关键字,创建一个简洁的名字。
#define do_forever for(;;) //用更形象的符号来替换一种实现。
#define CASE break;case //在写case语句的时候自动把break写上。// 如果#define定义的stuff过长,可以分成几行来写,除了最后一行外,
//每行的后面都加上一个反斜杠\(续航符)
#define DEBUG_PRINT printf("file:%s\tline:%d\t\date:%s\ttime:%s\n", \__FILE__,__LINE__ \__DATE__,__TIME__)
思考一个问题:#define定义标识符时,要不要在最后加上;
号?
比如:
#define MAX 1000;
#define MAX 1000
我的建议是不要加;
号。你别看上面的代码可以正常的运行,但是针对某些特定的应用场景,可能会引发一些难以察觉的错误。
比如下面的例子:
#define MAX 1000;
int main()
{int max = 0;int condition = 1;if(condition)max = MAX;elsemax = 0;
}
上述代码直接运行会报错,而错误的原因是悬空else的问题。因为MAX本身就拥有了一个;
号,而我们在代码写的分号会被是作为一个空语句,也就是说,if之后else之前由两条语句。但是如果要在if后里面写多条语句就得有大括号括起来。否则,就会报语法错误。
3. #define定义宏
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现我们通常称为宏或者定义宏。
下面时宏的声明方式:
#define name(parament-list) stuff
其中,parament-list是一个由逗号分隔的符号表,它们可能出现在stuff中。
注意:参数列表的左括号一定要与name紧邻。如果两者之间有空格的话,参数列表就会被编译器解释为stuff中的一部分。
举例:
#define SQUARE(x) x*x
这个宏接受了一个参数x。如果在上述声明过后,把SQUARE(5);
置于程序中,与编译器就会用5*5这个表达式来替换SQUARE(5)
。
但是,我们写的这一个宏有潜在的隐患。为什么这么说呢?
请看下面的例子:
#include<stdio.h>
#define SQUARE(x) x*x
int main()
{int a = 5;printf("%d\n",SQUARE(a + 1));return 0;
}
哎呦,这里的答案不是36吗,为什么这里会打印出11?
其实,这是直接替换文本的弊端,它是直接替换的。也就是说,先前的printf里的参数变为了
printf("%d",a+1*a+1);
这样说的话就比较清晰了,有替换产生的表达式并没有按照我们的预期顺序进行运算求值。
那我们该怎么修改上述的代码,使其能够得到正确的答案呢?
方法很简单,就是加括号,改变运算符的优先级。
#include<stdio.h>
#define SQUARE(x) (x)*(x)
int main()
{int a = 5;printf("%d\n",SQUARE(a + 1));return 0;
}
这样就达到了预期的效果了。
为了巩固大家加括号的意识,我再举一个例子。
这里还有一个宏定义:
#define DOUBLE(x) (x) + (x)
在定义中我们为了避免预算符之间的优先级和结合性,我们给其添上了括号,但是这个宏仍然会出现问题。
int a = 5;
printf("%d",10*DOUBLE(a));
这个会打印出什么结果呢?看上去好像是100,但事实上打印的值确是55。
我们发现替换之后:
printf("%d",10*(5)+(5));
乘法运算的优先级高于加法,所以就会出现55.
为了解决这个问题,我们可以这样写:
#define DOUBLE(x) ((x)+(x))
以上两个例子告诉我们,在写宏时,一定不要节省你的括号。
4. 带有副作用的宏参数
什么叫带有副作用?
请大家看下面几段代码:
int a = 5;
int b = 0;
b = a + 1; //方案1
b = a++; //方案2
在方案1中,a的值仍然为5 。但在方案2中,a的值就变为6了。相信讲到这里你已经有点感觉了。
所谓带有副作用其实就是以修改参与运算变量的值为代价,实现我们要到达的效果。
当宏参数在宏的定义中出现超过一次的情况,如果参数带有副作用,那么你在使用这个宏的时候就有可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性的效果。
这里我们设置一段代码来证明带有副作用的宏参数所引发的问题:
#define MAX(a,b) ((a>b)?(a):(b))
...
x = 5;
y = 8;
z = MAX(x++,y++);
printf("x=%d,y=%d,z=%d\n",x,y,z);//输出的结果是什么?
输出的结果为:x=6,y=10,z=9
5. 宏替换的规则
在程序中扩展使用#define定义符号和宏,需要涉及几个步骤:
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果有,它们首先被替换。
- 替换后的文本会被插入到程序中原来文本的位置。对于宏来说,参数名被它们的值所替代。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果有则重复上述步骤。
注意:
- 宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏来说,不能出现递归。
- 当预处理器搜索#define定义的符号的时候,字符串常量的内容不在搜索范围。
限于篇幅的原因,本文就像先讲到这里。后续的内容都在预处理详解(下)中,欢迎大家指点一二。💖💖💖
相关文章:

【C语言】预处理详解(上)
文章目录 前言1. 预定义符号2. #define 定义常量3. #define定义宏4. 带有副作用的宏参数5. 宏替换的规则 前言 在讲解编译和链接的知识点中,我提到过翻译环境中主要由编译和链接两大部分所组成。 其中,编译又包括了预处理、编译和汇编。当时,…...

uni-app内置组件(基本内容,表单组件)()二
文章目录 一、 基础内容1.icon 图标2.text3.rich-text4.progress 二、表单组件1.button2.checkbox-group和checkbox3.editor 组件4.form5.input6.label7.picker8.picker-view 和 picker-view-column9.radio-group 和 radio10.slider11.switch12.textarea 一、 基础内容 1.icon…...

linux搭建redis超详细
1、下载redis包 链接: https://download.redis.io/releases/ 我以7.0.11为例 2、上传解压 mkdir /usr/local/redis tar -zxvf redis-7.0.11.tar.gz3、进入redis-7.0.11,依次执行 makemake install4、修改配置文件redis.conf vim redis.conf为了能够远程连接redis…...

Flink-DataWorks第二部分:数据集成(第58天)
系列文章目录 数据集成 2.1 概述 2.1.1 离线(批量)同步简介 2.1.2 实时同步简介 2.1.3 全增量同步任务简介 2.2 支持的数据源及同步方案 2.3 创建和管理数据源 文章目录 系列文章目录前言2. 数据集成2.1 概述2.1.1 离线(批量)同步…...

4个从阿里毕业的P7打工人,当起了包子铺的老板
吉祥知识星球http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247483727&idx1&sndb05d8c1115a4539716eddd9fde4e5c9&chksmc0e47813f793f105017fb8551c9b996dc7782987e19efb166ab665f44ca6d900210e6c4c0281&scene21#wechat_redirect 《网安面试指南》h…...

javaweb_07:分层解耦
一、三层架构 (一)基础 在请求响应中,将代码都写在controller中,看起来内容很复杂,但是复杂的代码总体可以分为:数据访问、逻辑处理、接受请求和响应数据三个部分。在程序中我们尽量让一个类或者一个方法…...
调用 Python 开源库,获取油管英文视频的手动或自动英文srt字幕,以及自动中文简体翻译srt字幕
前提条件 非常抱歉,这个程序就是个雏形,非常不完善,输入需要手动编辑,凑活着可以用,请自己完善吧。 开源声明:此文代码引用了一个开源MIT License的Python库,其他代码是本人自写自用。你可以随…...
UDP协议实现通信与数据传输(创建客户端和服务器)
目录 一、UDP (传输层,用户数据报协议) 二、服务器Server的创建 三、客户端Client的创建 四、效果实现(描述) 一、UDP (传输层,用户数据报协议) UDP(User Datagram Pr…...

【红黑树】
红黑树 小杨 红黑树的概念 红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍&am…...

排序算法——简单选择排序
一、算法原理 简单选择排序是一种基本的排序算法,其原理是每次从未排序的元素中选择最小(或最大)的元素,然后与未排序部分的第一个元素交换位置,直到所有元素都被排序。 二、算法实现流程 简单选择排序法(Simple Se…...

OpenAI API推出结构化输出功能
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...

Python 异步编程:Sqlalchemy 异步实现方式
SQLAlchemy 是 Python 中最流行的数据库工具之一,在新版本中引入了对异步操作的支持。这为使用异步框架(如 FastAPI)开发应用程序带来了极大的便利。在这篇文章中,简单介绍下 SQLAlchemy 是如何利用 Greenlet 实现异步操作的。 什…...
父类引用指向子类对象
在 Java 中,父类引用可以指向子类对象,这是多态的一种表现。这种特性允许你使用父类的引用来操作子类对象,从而实现更灵活和可扩展的代码设计。 基本概念 多态:父类引用可以指向子类对象。这使得你可以用统一的接口处理不同的对象…...

分享一个基于Spring Boot的面向社区的智能化健康管理系统的设计与实现(源码、调试、LW、开题、PPT)
💕💕作者:计算机源码社 💕💕个人简介:本人 八年开发经验,擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等,大家有这一块的问题可以一起交流&…...
【扒代码】reduction参数是什么
model DensityMapRegressor(in_channels256, reduction8)reduction 参数在 DensityMapRegressor 类中用于决定模型在上采样过程中的层级配置。具体来说,它决定了上采样过程中使用多少个 UpsamplingLayer,从而影响输出的分辨率。 reduction 参数的作用 …...

Python,Spire.Doc模块,处理word、docx文件,极致丝滑
Python处理word文件,一般都是推荐的Python-docx,但是只写出一个,一句话的文件,也没有什么样式,就是36K。 再打开word在另存一下,就可以到7-8k,我想一定是python-docx的问题,但一直没…...

redis的安装与命令
一、redis与memcache总体对比 1.性能 Redis:只使用单核,平均每一个核上Redis在存储小数据时比Memcached性能更高。 Memcached:可以使用多核,而在100k以上的数据中,Memcached性能要高于Redis。 2.内存使用效率 Mem…...

【C++】特殊类设计类型转换
目录 💡前言一,特殊类设计1. 请设计一个类,不能被拷贝2. 请设计一个类,只能在堆上创建对象3. 请设计一个类,只能在栈上创建对象4. 请设计一个类,不能被继承5. 请设计一个类,只能创建一个对象(单…...
为git 命令行 设置代理环境变量
http://t.csdnimg.cn/cAxkg 国内需要修改pinoko根目录下gitconfig文件,添加 [http]proxy http://127.0.0.1:1080 [https]proxy https://127.0.0.1:1080或者通过命令行配置: git config --global http.proxy http://127.0.0.1:1080 git config --glo…...
自定义linux某些常见配置
1.当前路径 echo "PS1\u\h:\w\$ " >> /etc/profile source /etc/profile 2.ssh使能 1.开启openssh 2.权限赋予chown root.root /var/empty/ 3.开发板作为server echo "PermitRootLogin yes" >> /etc/ssh/sshd_config 3开机自启动脚本 1.init…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...

selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...