【C语言】什么是宏定义?(#define详解)

🦄个人主页:修修修也

🎏所属专栏:C语言

⚙️操作环境:Visual Studio 2022


目录

一.什么是宏定义

二.宏定义的组成

第1部分

第2部分

第3部分

三.宏定义的应用

🎏类对象宏

🎏类函数宏

1.求两个数中的较大值

2.求一个数的平方值

3.求结构体成员偏移量

四.宏定义陷阱 

1.小白写法

2.码农写法

3.工程师写法

4.大牛写法

五.类函数宏与函数的对比

结语


一.什么是宏定义

在我们看球赛时,常常会留意到许多球星,比如:梅西,姆巴佩,乔丹,科比等等...,但我们也知道,"梅西","乔丹"等这类称呼并不是他们的本名,而是国内的人们为了方便称呼他们而起的昵称.

如梅西的名字实际上是:Lionel Andrés Messi Cuccitini(利昂内尔·安德烈斯·梅西·库奇蒂尼),但在国内,你只需要和对方说:"梅西",对方便知道你说的是那个Lionel Andrés Messi Cuccitini的"梅西".

这是因为,用"梅西"来代替"Lionel Andrés Messi Cuccitini"已经是国内人们约定俗成的观念了,而这样类似的用"替换"的方式使用一个简短的名称来代称一个繁杂的名称,在C语言中,我们称之为------宏定义(#define).

宏定义在C语言源程序中允许用一个标识符来表示一个字符串,称为“宏” ,被定义为“宏”的标识符称为“宏名”.

 如:

#define 梅西 Lionel Andrés Messi Cuccitini

以上就是一个宏定义,该定义是用"梅西"来表示"Lionel Andrés Messi Cuccitini"
其中,"梅西"这个标识符被称为宏名.

而Lionel Andrés Messi Cuccitini则是被表示的"字符串",这个"字符串"可以是常数,表达式,格式串等等.

在编译预处理时,对程序中所有出现的宏名,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”.

宏定义是由源程序中的宏定义命令完成的,宏代换是由预处理程序自动完成的.

编译器会在编译期间对所有的常量表达式(只包含常量的表达式)求值,预处理器不做计算,不对表达式求值,它只进行替换.

C程序运行过程图示


二.宏定义的组成

每行#define(逻辑行)都由3部分组成:

第1部分

#define指令本身.

(在C语言中凡是以“#”开头的均为预处理命令)

第2部分

选定的缩写,也称为宏.

有些宏代表值,这些宏被称为类对象宏(object-like macro),如下例:

类对象宏中不接收参数,只是根据宏定义做简单的字符串替换操作.

C语言还有类函数宏(function-like macro),如下例:

类函数宏不仅进行简单的字符串替换,而且还要包含参数的替换.

tips:宏的名称中不允许有空格,而且必须遵守C变量的命名规则:只能使用字符,数字和下划线( _ )字符,而且首字符不能是数字.

第3部分

(指令行的其余部分)称为替换列表或替换体.

一旦预处理器在程序中找到宏的示实例后,就会用替换体代替该宏.

从宏变成最终替换文本的过程称为宏展开.

注意,可以在#define行使用标准C注释.每条注释在预处理后都会被一个空格代替.

当然,宏定义还可以包含其他宏(有一些编译器不支持这种嵌套功能),比如:

#define X 3
#define Y 5#define MAX(X,Y) X>Y?X:Yint main()
{printf("%d", MAX(X, Y));return 0;
}

如上程序,宏定义MAX中包含了宏定义X和Y,vs2022中运行结果如下:

可见,宏定义是允许嵌套调用的.

一般而言,预处理器发现程序中的宏后,会用宏等价的替换文本进行替换,如果替换的字符串中还包含宏,则继续替换这些宏.

唯一例外的是双引号中的宏,如:

这时因为第二个宏X被双引号引起来了,导致其不被编译器识别为宏,而识别为一个没有特殊含义的字符串了.


三.宏定义的应用

🎏类对象宏

宏定义中的类对象宏的应用场景大致分为以下几种:

首先,对于绝大部分数字常量,我们应该使用宏定义来表示它们.

如果在算式中用宏定义代替数字,常量名能更清楚的表达该数字的含义,如:

#define PI 3.14  /*表示圆周率常量*/int main()
{int r = 2;double area = 0;area = r * r * PI;   /*计算圆的面积area*/return 0;
}

其次,如果是表示数组大小的数字,用符号常量后更容易改变数组的大小和循环次数,如:

#define ROW 5
#define COL 5int main()
{int arr[ROW][COL];//使用宏定义创建一个二维数组return 0;
}

最后,如果数字是系统代码(如,EOF),用宏定义表示的代码更容易移植(只需要改变EOF的定义).

#define EOF -1
#define True 1
#define False 0

宏定义有价值的特性包括:助记,易更改,可移植.


🎏类函数宏

1.求两个数中的较大值

在C语言初学阶段,我们学习过怎样编写一个函数求两个数中的较大值,如:

int Move_Max(int x, int y)
{return x>y?x:y;
}int main()
{int x = 3;int y = 5;int max = 0;max=Move_Max(x, y);printf("%d",max);return 0;
}

运行程序,得到结果:

在我们学习了宏定义后,我们可以借助宏定义和三目运算符来完成这一功能,如:

#define MAX(X,Y) X>Y?X:Yint main()
{int x = 3;int y = 5;int max = 0;max = MAX(x, y);printf("%d", max);return 0;
}

该程序运行时,第9行代码会被替换成:

max = x>y?x:y ;

运行程序,得到结果:


2.求一个数的平方值

同样的,我们学习过怎样编写一个函数求一个数的平方值,如:

int Move_Square(int x)
{return x * x;
}int main()
{int x = 3;int square = 0;square = Move_Square(x);printf("%d", square);return 0;
}

运行程序,得到结果:

再试试使用宏定义来实现这一功能:

#define Square(X) X*Xint main()
{int x = 3;int square = 0;square = Square(x);printf("%d", square);return 0;
}

该程序运行时,第8行代码会被替换成:

square = x*x ;

运行程序,得到结果:


3.求结构体成员偏移量

C语言中有这样一个库宏offsetof:

 offsetof是一个宏,在C语言中用于获取结构体成员相对于结构体起始地址的偏移量(以字节为单位)。

包含在<stddef.h>头文件中

通过指定结构体类型成员名称作为参数,offsetof宏会返回该成员在结构体中的偏移量

(不懂如何计算结构体成员偏移量的可以移步我的这篇博客:【C语言】结构体的大小是如何计算的?(结构体对齐))

我们在vs2022中测试一下该宏:

我们接下来使用宏定义模仿实现一下这个库宏:

#include<stdio.h>
#define MY_OFFSETOF(type,member)  (size_t)&(((type*)0)->member)struct stu {char ch;int sz;short age;
};int main()
{printf("%d\n", MY_OFFSETOF(struct stu, ch));printf("%d\n", MY_OFFSETOF(struct stu, sz));printf("%d\n", MY_OFFSETOF(struct stu, age));return 0;
}

 测试运行,得到结果:

有关更多库宏offsetof的详解可以移步我的另一篇博客: 【C语言】库宏offsetof详解


四.宏定义陷阱 

即便使用宏定义看似简便,高效,但宏定义中同样存在一些陷阱,接下来我们将会以三目运算符求两个数中的较小值为例,向大家展示宏定义中可能一不小心就被大家忽略的陷阱:

1.小白写法

#define MIN(A,B) A < B ? A : B

然后我们使用这个宏定义:

int a = MIN(1, 2);

该代码在预处理结束后会被替换为:

int a = 1 < 2 ? 1 : 2;
int a = 1;

该定义的问题:

当我们需要这样使用这个宏定义时:

int a = 2 * MIN(3, 4);

我们以为得到的结果会是:

int a = 2 * 3;
int a=6;

但实际上我们得到的结果是:

int a = 2 * 3 < 4 ? 3 : 4 ;
int a = 6 < 4 ? 3 : 4 ;
int a = 4 ;

2.码农写法

上段代码的问题在于没有保证宏体被替换后整体的优先级最高,因此我们修改一下上面的宏定义,给后面的表达式整体带上括号,使宏体在被替换后仍能保证优先级最高:

#define MIN(A,B) (A < B ? A : B)

这下我们再像刚才那样使用这个宏定义:

int a = 2 * MIN(3, 4);

该代码在预处理后会被替换为:

int a = 2 * (3 < 4 ? 3 : 4);
int a = 2 * 3;
int a = 6;

该定义的问题:

 当我们需要这样使用这个宏定义时:

int a = MIN(3, 4 < 5 ? 4 : 5);

我们以为得到的结果会是:

int a = MIN(3,4);
int a = 3 < 4 ? 3 : 4;
int a = 3;

但实际上我们得到的结果是:

int a = ( 3 < 4 < 5 ? 4 : 5 ? 3 : 4 < 5 ? 4 : 5 );
//按照操作符的优先级给上面的式子加上括号便于理解
int a = ((3 < (4 < 5 ? 4 : 5) ? 3 : 4) < 5 ? 4 : 5); 
int a = ((3 < 4 ? 3 : 4) < 5 ? 4 : 5)
int a = (3 < 5 ? 4 : 5)
int a = 4

3.工程师写法

上段代码的问题在于没有考虑到宏参数是表达式的情况,导致宏展开后参数运算的优先级不是最高的,因此我们修改一下上面的宏定义,给参数带上括号,使宏展开后参数的运算优先级是最高的:

#define MIN(A,B) ((A) < (B) ? (A) : (B))

 这下我们再像刚才那样使用这个宏定义:

int a = MIN(3, 4 < 5 ? 4 : 5);

该代码在预处理后会被替换为:

int a = ( (3) < (4 < 5 ? 4 : 5) ? (3) : ( 4 < 5 ? 4 : 5) );
int a = ( 3 < 4 ? 3 : 4 );
int a = 3;

该定义的问题:

 当我们需要这样使用这个宏定义时:

float a = 1.0f;
float b = MIN(a++, 1.5f);

我们以为得到的结果会是:

float b = MIN(1.0f,1.5f);
float b = 1.0f;

但实际上我们得到的结果是:

float b = ((a++) < (1.5f) ? (a++) : (1.5f));
float b = ( 1.0f < 1.5f ? 2.0f : 1.5f );
float b = 2.0f;

4.大牛写法

上面代码的问题在于没有考虑到自增/自减类参数在宏展开后会有副作用,我们再修改该宏使之达到完美:

#define MIN(A,B)    ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; })

五.类函数宏与函数的对比

类函数宏的调用看上去和函数调用相同,那么这两者有何区别呢?


结语

在本文中我们介绍了宏定义的概念,组成及其应用,还拓展了宏定义的易错陷阱,以及类函数宏与函数的优劣对比,希望能对大家有所帮助.

相关文章推荐

【C语言】库宏offsetof

【C语言】结构体的大小是如何计算的?(结构体对齐)



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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

测试时间不够,你会如何处理?

工作中经常会遇到测试时间不够充分的情况&#xff0c;当测试时间不足的情况下&#xff0c;如何做到不延误测试进度&#xff0c;又能保证测试质量&#xff1f; 1、根据测试目标和需求&#xff0c;确定测试的优先级&#xff0c;首先测试最重要和核心的功能和场景。 确保关键功能…

一文搞懂时间序列ARIMA模型

文章目录 1 ARIMA的定义2 差分(differencing)2.1 Order&#xff1a;差分的阶数2.2 Lag&#xff1a;差分的滞后2.3 滞后运算/滞后算子/延迟算子2.4 关于差分的两个误解 3 ARIMA的平稳性4 ACF与PACF5 时序模型的选择与评估5.1 超参数p、q、d的确定5.2 时间序列的评估指标 1 ARIMA…

【网络安全-信息收集】网络安全之信息收集和信息收集工具讲解(提供工具)

工具下载百度网盘链接(包含所有用到的工具&#xff09;&#xff1a; 百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全稳固&#xff0c;支持教育网加速&#xff0c;支持手机端。注册使用百度网盘即可享受免费存储空间https://pan.…

Canvas--》使用Canvas完成基本绘图

&#x1f31f;Canvas介绍 <canvas>是一个可以使用脚本 (通常为javaScript) 来绘制图形的HTML元素。例如&#xff0c;它可以用于绘制图表、制作图片构图或者制作简单的动画。如上面效果示例就是使用 <canvas> 来实现示例&#xff0c;后续将一步步实现上面效果。 C…

2023-10-06 LeetCode每日一题(买卖股票的最佳时机含手续费)

2023-10-06每日一题 一、题目编号 714. 买卖股票的最佳时机含手续费二、题目链接 点击跳转到题目位置 三、题目描述 给定一个整数数组 prices&#xff0c;其中 prices[i]表示第 i 天的股票价格 &#xff1b;整数 fee 代表了交易股票的手续费用。 你可以无限次地完成交易&…

C++树详解

树 树的定义 树&#xff08;Tree&#xff09;是n&#xff08;n≥0&#xff09;个结点的有限集。n0时称为空树。在任意一颗非空树中&#xff1a;①有且仅有一个特定的称为根&#xff08;Root&#xff09;的结点&#xff1b;②当n>1时&#xff0c;其余结点可分为m&#xff08…

HP 喷墨一体机 - “检查墨盒“指示灯闪烁,怎么办?

适用机型&#xff1a; HP PSC 1118、1218 、1318、1350、1406/1408、1508 故障现象&#xff1a; “检查墨盒”指示灯闪烁&#xff0c;“份数”显示的是英文字母“E”&#xff0c;代表 Error&#xff08;错误&#xff09;的意思。&#xff08;无复印份数显示 &#xff09; “检…

Solidity 合约漏洞,价值 38BNB 漏洞分析

Solidity 合约漏洞&#xff0c;价值 38BNB 漏洞分析 1. 漏洞简介 https://twitter.com/NumenAlert/status/1626447469361102850 https://twitter.com/bbbb/status/1626392605264351235 2. 相关地址或交易 攻击交易&#xff1a; https://bscscan.com/tx/0x146586f05a451313…

基于SSM+Vue的鲜花销售系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用Vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

16哈希表-基础操作

目录 哈希表 散列思想 哈希表的实现 简单示例 开胃菜&#xff1a;LeetCode之路——242. 有效的字母异位词 分析 哈希表 英文名字为Hash table&#xff0c;散列表的英文叫“Hash Table”&#xff0c;我们平时也叫它“哈希表”或者“Hash表”。 哈希表&#xff08;Hash Ta…

typescript 类型声明文件

typescript 类型声明文件概述 在今天几乎所有的JavaScript应用都会引入许多第三方库来完成任务需求。这些第三方库不管是否是用TS编写的&#xff0c;最终都要编译成JS代码&#xff0c;才能发布给开发者使用。6我们知道是TS提供了类型&#xff0c;才有了代码提示和类型保护等机…

盒子模型的基础

盒子模型 边框&#xff08;border&#xff09; border可以设置元素的边框&#xff0c;边框分成三部分&#xff0c;边框的&#xff08;粗细&#xff09;边框的样式&#xff0c;边框的颜色 <style>div {width: 100px;height: 100px;border-width: 200;border-style: 边框…

时序分解 | Matlab实现CEEMDAN完全自适应噪声集合经验模态分解时间序列信号分解

时序分解 | Matlab实现CEEMDAN完全自适应噪声集合经验模态分解时间序列信号分解 目录 时序分解 | Matlab实现CEEMDAN完全自适应噪声集合经验模态分解时间序列信号分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现CEEMDAN完全自适应噪声集合经验模态分解时间…

基于SSM的旅游网站设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

Springboot+vue的开放性实验室管理系统(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的开放性实验室管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的开放性实验室管理系统&#xff0c;采用M&#xff08…

python开发幸运水果抽奖大转盘

概述 当我女朋友跟我说要吃水果&#xff0c;又不知道吃啥水果时候&#xff0c;她以为难为到我了&#xff0c;有啥事难为到程序员的呢&#xff01; 今天用python利用第三方tkinterthreadingtime库开发一个幸运水果抽奖大转盘&#xff01;抽到啥吃啥 详细 老规矩&#xff01;咱…

Qt 设置软件的版本信息:QMake、CMake工程

本文借鉴了Qt 设置软件的版本信息 - 疯狂delphi - 博客园 (cnblogs.com) 在原文基础增加了CMake工程实现的方法。 Qt设置软件的版本等信息 对于Qt开发的软件&#xff0c;我们如何去方便的查看其软件的版本信息。这里提供了几种方式。 在运行程序期间设置版本信息 大部分的程序…

CDN是什么?(网络零基础入门篇)

1.CDN的全称 是 Content Delivery Network&#xff0c;即内容分发网络。 其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节&#xff0c;使内容传输得更快、更稳定。 通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网…

黑豹程序员-架构师学习路线图-百科:JSON替代XML

文章目录 1、数据交换之王2、XML的起源3、JSON诞生4、什么是JSON 1、数据交换之王 最早多个软件之间使用txt进行信息交互&#xff0c;缺点&#xff1a;纯文本&#xff0c;无法了解其结构&#xff1b;之后使用信令&#xff0c;如&#xff1a;电话的信令&#xff08;拨号、接听、…

高数:第三章:一元函数积分学

文章目录 一、不定积分(一)两个基本概念&#xff1a;原函数、不定积分(二)原函数的存在性&#xff1a;原函数存在定理(三)不定积分的性质(四)基本积分公式(五)三种主要积分法1.凑微分 (第一类换元法)2.换元法 (第二类换元法)①三角代换②根式代换③倒代换 3.分部积分法4.其他技…