【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…...

告别手动操作!KeyMouseGo实现自动化工作流
前言 在这个快节奏的时代,我们每天都在与电脑打交道,重复着那些繁琐而单调的操作;你是否曾想过,能让电脑自己完成这些任务,而你则悠闲地喝着咖啡,享受着生活?今天,就让我们一起揭开一…...

大型语言模型微调 新进展-4篇 论文
1. Brevity is the soul of wit: Pruning long files for code generation 发布时间:2024-06-29链接:https://arxiv.org/abs/2407.00434机构:伦敦大学学院 (UCL) 本研究针对大型语言模型的代码生成任务中的数据清理问题进行了探索。研究发现…...

专业课140+杭电杭州电子科技大学843信号与系统考研经验电子信息与通信工程真题,大纲,参考书。
顺利上岸杭电,由于专业课考的不错140,群里不少同学希望分享一点经验,回头看看这一年考研复习,确实有得有失,总结一下自己的专业课复习经验,希望对大家有帮助,基础课考的没有专业好,而…...

php 中 (0 == ‘abc‘) 为真
https://andi.cn/page/621653.html...

MacOS Anaconda 安装教程及虚拟环境创建
一、下载 Anaconda 1、Anaconda 官网 2、清华大学开源软件镜像站 点 Date 按时间排序,根据自己 Mac 芯片类型下载对应最新版本的。 Intel 芯片的下载 x86_64 版本的Apple m1 芯片的下载 arm64 版本的 二、安装 Anaconda 将安装包下载到本地后,双击安…...

Mac快速配置ADB环境变量
ADB是进行 Androd 开发时很常用的调试工具,Android SDK 中就包含了该工具,所以如果安装了SDK那只需要在环境变量中配置 Android SDK 的路径即可,本文的环境配置也基于这种场景。 如果需要独立下载 ADB 工具,请参考下面网址&#x…...

Kylin的工作原理及使用分享
前言 在当今信息爆炸的时代,企业和研究机构每天都在生成和收集大量的数据。这些数据中蕴藏着巨大的商业价值和研究潜力,但要从中提取出有用的信息却并非易事。传统的数据处理和分析技术在面对如此庞大的数据量时,往往难以提供快速和有效的响…...

python 使用seleniumwire获取响应数据
seleniumwire 是一个在 Selenium WebDriver 基础上扩展的库,它允许你在使用 Selenium 进行网页自动化测试或爬虫时捕获和修改 HTTP 请求和响应。这对于需要分析网页数据或进行更复杂的网络交互的自动化任务特别有用。 以下是如何使用 seleniumwire 来获取响应数据的…...

用C语言实现双向链表
目录 一.双向链表的结构 二. 双向链表的实现 1. 在List.h中结构体的定义和各函数的声明 1.1 结构体(节点)的定义 1.2 各函数的声明 2. 在List.c中各函数的实现 2.1 初始化 LTInit 2.2 尾插 LTPushBack 2.3 打印 LTPrint 2.4 头插 LTPushFron…...

Github 2024-08-10 Rust开源项目日报Top10
根据Github Trendings的统计,今日(2024-08-10统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Rust项目10Python项目1Turbo:下一代前端开发工具链 创建周期:977 天开发语言:Rust协议类型:MIT LicenseStar数量:25308 个Fork数量:1713 …...