C语言预处理操作详解

这篇博客和大家分享一下C语言中的预处理操作。

1. 预定义符号

C语言设置了⼀些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的。

__FILE__     //进行编译的源文件

__LINE__     //文件当前的行号

__DATA__    //文件被编译的日期

__TIME__    //文件被编译的时间

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

一起看下面的代码,我们来举个栗子: 

#include<stdio.h>
int main()
{
  printf("%s\n",__FILE__);
  return 0;
}

2. #define定义常量

基本语法:

#define name stuff

 看下面几个例子:

#define MAX 100 //为MAX定义一个值
#define reg register  //为register关键字创建一个简短的名字
#define CASE break;case  //再写case语句的时候自动把break补上
//如果定义的 stuff过⻓,可以分成⼏⾏写,除了最后⼀⾏外,每⾏的后⾯都加⼀个反斜杠(续⾏符)。

在定义标识符的时候要不要加上“ ; ”?建议不要加上,容易导致问题,比如下面的场景:

if(a==1)
  max=MAX;
else
  max=0;

如果是这种情况下加了分号的话,那么等替换的时候,if和else之间就是两条语句,没有括号的情况下,if后面只能有1条语句,会出现语法错误。

3. #define定义宏

#define机制包括一个规定,允许把参数替换到文本中,这种实现通常叫做宏或者定义宏,宏的申明方式如下:

#define name(parament-list) stuff

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

#define SQUARE(x) x*x

这是一个宏接收参数x,如果在上述声明之后,会将SQUARE(5)置于程序中,预处理器就会用下面的式子来替换掉上面的式子:x*x。这个宏存在一个问题,观察下面的代码:

int a=4;
printf("%d",SQUARE(a+1));

运行一下我们看一下结果:

这时候是不是在想不应该是16吗?为什么是9呢? 在预处理器替换文本时,x变成了a+1;所以这条语句就变成了:

printf("%d",a+1*a+1);

在宏定义上加上两个括号问题就解决了:

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

这样预处理之后就解决这个问题了,那么我们看一下下面这宏:

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

在定义时我们使用了括号避免了刚刚的问题,但同时又带了新的问题,我们看下面的代码:

int a=5;
printf("%d",10*ADD(a));

运行一下看看结果如何:

这里我们已经使用了上面的方法加括号了,结果不应该是100吗?为什么是55呢?乘法咸鱼宏的定义计算,在预处理时这条语句就变成了:

printf("%d",10*(5)+(5));

 注意:所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

4. 带有副作用的宏参数

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

x+1;//没有副作用
x++;//有副作用

我们可以定义一个MAX宏来证明具有副作用的参数所引起的问题。

#define MAX(a,b) ((a)>(b)?(a):(b))
....//省略一些常规代码
a=5;
b=9;
c=MAX(a++,b++);
printf("%d %d %d",a,b,c);

输出结果是什么呢?我们调试一下看看结果:

 为什么不是5 9 11呢?其实在c=MAX(a++,b++)这条语句被替换成了下面这条语句:

c=(a++)>(b++)?(a++):(b++);

在这段代码中第一个括号内的a++是先使用a的值再++,同理下一个括号的b也是先使用b的值再++,在这个三目运算符中,就变成了5和9的比较来决定返回的结果,再后面的括号中的a++,a已经经过前面的计算变成了6,同理b也就变成了10,然后再看返回的结果也是b++,那就是先使用再++,返回的是10,返回了一个值也就是使用了b,此时b还要再++一次,所以b的值就变成了11。

在这里大家有没有发现如果宏的参数是个表达式的话,宏的参数是不计算的,直接替换,把该替换的位置全部替换掉。

5. 宏替换的规则

在上面我们一直提到宏的替换,那么宏的替换规则是什么呢?

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

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

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

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

注意:1.宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。

2.当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

6. 宏和函数的对比

宏通常被应用于执行简单的运算。比如在两个数中找到较大的那一个,我们可以写:

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

那为什么不用函数来完成这个任务呢?主要原因是:1.用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜⼀筹。2.更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之
这个宏怎可以适用于整形、长整型、浮点型等可以用于 > 来比较的类型。宏是类型无关的。

这事就有人好奇了,你这一直说宏的好处,那我们为什么还要用函数呢?其实宏和函数对比还是有很大的劣势的:

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

2.宏是无法调试的。

3.宏由于与类型无关,也就不够严谨。

4.宏可能会带来运算符优先级的问题,导致程容易出现错。

宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。下面做一个宏和函数的对比:

属性#define定义宏函数
代码长度每次使用时,宏代码都会被插入到程序中,除了非常小的宏之外,代码长度会大幅增长函数代码只出现在一个地方,每次使用函数时,都会调用那里的同一份代码
执行速度更快存在函数的调用和返回的额外开销,所以相对较慢一些
操作符优先级宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则临近操作符的优先级可能会产生不可预料的后果,所以建议在定义宏时多些括号函数参数只在函数调用的时候求一次值,它的结果值传递给函数,表达式的求值更容易预测
带有副作用的参数参数可能被替换到宏体内多个位置,如果宏的参数被多次计算,带有副作用的参数求值会产生不可预料的结果函数参数只在传参时求值一次,结果更容易控制
参数类型宏的参数与类型无关,只要是对参数的操作是合法的,他就可以适用于任何类型的参数函数的参数与类型有关,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的
调试宏是不方便去调试的函数是可以逐语句调试的
递归宏不能递归函数是可以递归的

7.#和##

7.1 #运算符

#运算符将宏的⼀个参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。#运算符所执行的操作可以理解为“字符串化”。当我们有一个变量int a=10时,我们想打印出the value of a is 10时该怎么办呢?我们可以这样写:

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

当我们按照下面的方式调用时:PRINT(a)    当我们把a替换到宏的体内时,就出现了#a,而#a就是转换为(a)时,代码就会被处理为:

printf("the value of ""a"" is "%d"",a);

从而达到我们想要的结果

7.2 ##运算符

## 可以把位于它两边的符号合成⼀个符号,它允许宏定义从分离的⽂本片段创建标识符。 ## 被称
为记号粘合这样的连接必须产生⼀个合法的标识符。否则其结果就是未定义的。这里我们想想,写⼀个函数求2个数的较大值的时候,不同的数据类型就得写不同的函数。例如:

int int_max(int a,int b)
{
  return a>b?a:b;
}
float float_max(float a,float b)
{
  return a>b?a:b;
}

这样写起来太繁琐了,我们可以这样写:

#define GENERIC_MAX(type)
type type##_max(type x,type y)
{
  return (x>y?x:y);
}

使用宏定义函数,我们一起看下面的代码来感受一下:

GENERIC_MAX(int)
GENERIC_MAX(float)
int main()
{
  int m=int_max(2,3);
  printf("%d\n",m);
  float n=float_max(3.5f,4.5f);
  printf("%f\n",n);
  return 0;
}

8. 命名约定 

⼀般来讲函数的宏的使用语法很相似。所以语言本⾝没法帮我们区分⼆者。我们平时的一个习惯就是:把宏的名字全部大写,函数名不要全部大写。

9. #undef 

这条指令用来移除一个宏定义,用法如下:

#undef NAME

 本篇到这里就结束了,感谢大家的观看,有问题可以评论或者私信我。

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

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

相关文章

【Android】重温Activity生命周期

前言 Android中用得最多的组件是Activity&#xff0c;而它的生命周期也是最基础的知识&#xff0c;从刚接触Android到工作中会频繁依赖这部分知识。可能大多数人能说出页面新建到页面关闭会走的生命周期&#xff1a;onCreate、onStart、onResume、onPause、onStop、onDestory&…

【五十三】【算法分析与设计】1392. 最长快乐前缀,686. 重复叠加字符串匹配,796. 旋转字符串,KMP算法

目录 1392. 最长快乐前缀 思路 过程 686. 重复叠加字符串匹配 796. 旋转字符串 string内置函数find KMP算法 结尾 1392. 最长快乐前缀 「快乐前缀」 是在原字符串中既是 非空 前缀也是后缀&#xff08;不包括原字符串自身&#xff09;的字符串。 给你一个字符串 s&…

JAVA_类和对象(1)

认识面向对象 Java是一门纯面向对象的语言(Object Oriented Program, OOP)&#xff0c;在面向对象的世界里&#xff0c;一切皆为对象。面向对象是解决问题的一种思想&#xff0c;主要依靠对象之间的交互完成一件事情。  面向过程和面相对象并不是一门语言&#xff0c;而是解决…

OpenStack镜像管理与制作

一、OpenStack镜像服务 1、什么是镜像 镜像通常是指一系列文件或一个磁盘驱动器的精确副本。虚拟机所使用的虚拟磁盘&#xff0c;实际上是一种特殊格式的镜像文件。云环境下尤其需要镜像。镜像就是一个模板&#xff0c;类似于VMware的虚拟机模板&#xff0c;其预先安装基本的…

格雷希尔G80L-T系列大口径快速连接器,在汽车膨胀水箱的气密性测试密封方案

副水箱也有人称作膨胀水箱&#xff0c;是汽车散热系统的一个重要组成部分&#xff0c;当水箱里面的温度过高的时候就会产生一定的压力&#xff0c;而副水箱可以根据热胀冷缩来帮助水箱和发动机排出去多余的水&#xff0c;起到一个调节的作用&#xff0c;副水箱由PP/PE塑料注塑而…

单向链表的实现

前言&#xff1a;继顺序表后的又一个线性结构——链表&#xff0c;这里将单向链表的实现。 目录 链表简介: 多文件实现&#xff1a; SList.h&#xff1a; SList.c实现函数的详解&#xff1a; 因为插入数据需要创建节点&#xff0c;很频繁&#xff0c;所以直接将创建新节点分…

《中医病证分类与代码》-中医疾病分类数据库

《中医病症分类与代码》由国家中医药管理局2020年底修订&#xff0c;目的是为中医疾病及证候的分类提供统一的规范。规定2021年起&#xff0c;各中医机构的临床科室及基层中医药的医师都应按照最新修订的《中医病症分类与代码》规范来填报病案及病历。 中医病证分类与代码数据库…

C++STL详解(一)— string类

string 类对象的常见容量操作 函数名称 功能 size 返回字符串有效字符长度length返回字符串有效字符长度capacity返回空间总大小clear清空有效字符empty检测字符串是否为空串&#xff0c;是返回true,否则返回falsereserve对容量进行改变resize扩容初始化 size和length 文档解…

Linux系统(centos,redhat,龙芯,麒麟等)忘记密码,怎么重置密码

Linux系统&#xff08;centos,redhat,龙芯&#xff0c;麒麟等&#xff09;忘记密码&#xff0c;怎么重置密码&#xff0c;怎么设置新的密码 今天在操作服务器时&#xff0c;DBA忘记了人大金仓数据库的kingbase密码&#xff0c;他的密码试了好多遍&#xff0c;都不行。最后只能…

sublime text中文---功能强大、操作便捷的代码编辑神器

Sublime Text是一款极受欢迎的代码编辑器&#xff0c;以其出色的性能、丰富的功能和优雅的用户界面赢得了广大开发者的青睐。它支持多种编程语言&#xff0c;包括HTML、CSS、JavaScript、Python等&#xff0c;让开发者能够轻松编辑和调试各种代码。Sublime Text拥有强大的自定义…

配置路由器实现互通

1.实验环境 实验用具包括两台路由器(或交换机)&#xff0c;一根双绞线缆&#xff0c;一台PC&#xff0c;一条Console 线缆。 2.需求描述 如图6.14 所示&#xff0c;将两台路由器的F0/0 接口相连&#xff0c;通过一台PC 连接设备的 Console 端口并配置P地址&#xff08;192.1…

SpringBoot是如何实现main方法启动Web项目的?

一、问题解析 在Spring Boot中&#xff0c;通过SpringApplication类的静态方法run来启动Web项目。当我们在main方法中调用run方法时&#xff0c;Spring Boot使用一个内嵌的Tomcat服务器&#xff0c;并将其配置为处理Web请求。 当应用程序启动时&#xff0c;Spring Boot会自动扫…

C#学习笔记11:winform上位机与西门子PLC网口通信_下篇

今日终于到了winform上位机与西门子PLC网口通信的系列收为阶段了&#xff0c;一直没一口气更新完&#xff0c;手头上也没有可以测试用的PLC设备&#xff0c;虚拟仿真用到的博图软件也不想下载&#xff08;会让我电脑变卡&#xff09;。 于是等了些日子购买西门子PLC&#xff0…

JIT在汽车行业中的革命性应用:颠覆传统制造模式,引领智能制造新时代

随着科技的飞速发展和市场竞争的日益激烈&#xff0c;汽车行业正面临着前所未有的变革。其中&#xff0c;准时制生产&#xff08;Just-In-Time&#xff0c;简称JIT&#xff09;作为一种先进的生产管理方式&#xff0c;已经在汽车行业中得到了广泛应用&#xff0c;成为推动汽车产…

密码学 | 椭圆曲线 ECC 密码学入门(三)

目录 7 这一切意味着什么&#xff1f; 8 椭圆曲线密码学的应用 9 椭圆曲线密码学的缺点 10 展望未来 ⚠️ 原文地址&#xff1a;A (Relatively Easy To Understand) Primer on Elliptic Curve Cryptography ⚠️ 写在前面&#xff1a;本文属搬运博客&#xff0c;自己留…

论文略读:Window Attention is Bugged: How not to Interpolate Position Embeddings

iclr 2024 reviewer 打分 6666 窗口注意力、位置嵌入以及高分辨率微调是现代Transformer X CV 时代的核心概念。论文发现&#xff0c;将这些几乎无处不在的组件简单地结合在一起&#xff0c;可能会对性能产生不利影响问题很简单&#xff1a;在使用窗口注意力时对位置嵌入进行插…

DC-1渗透测试复现

DC-1渗透测试复现 目的&#xff1a; 获取最高权限以及5个flag 过程&#xff1a; 信息打点-cms框架漏洞利用-数据库-登入admin-提权 环境&#xff1a; 攻击机&#xff1a;kali(192.168.85.136) 靶机&#xff1a;DC_1(192.168.85.131) 复现&#xff1a; 一.信息收集 扫…

IDEA 本地库引入了依赖但编译时找不到

在使用 IDEA 开发 Maven 项目的过程中&#xff0c;有时会遇到本地库引入了依赖&#xff0c;但编译时报找不到这个依赖&#xff0c;可以使用命令处理。 打开 Terminal。 执行清理命令。 mvn clean install -Dmaven.test.skiptrue执行更新命令。 mvn -U idea:idea

怎么清除3D模型杂质?---模大狮模型网

在进行3D建模过程中&#xff0c;模型可能会受到各种杂质的影响&#xff0c;这些杂质可能来自于模型本身的结构问题、导入导出过程中的错误、或者是不当的编辑操作所留下的痕迹。清除这些杂质是保证模型质量和渲染效果的关键步骤之一。本文将介绍几种常见的清除3D模型杂质的方法…

【C++】适配器· 优先级队列 仿函数 反向迭代器

目录 适配器&#xff1a;适配器的应用&#xff1a;1. 优先级队列&#xff1a;仿函数&#xff1a;更深入的了解仿函数&#xff1a;一个关于不容易被注意的知识点&#xff1a; 2. 反向迭代器&#xff1a;list && vector&#xff1a; 适配器&#xff1a; 我们先来谈来一下…