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

news2024/11/20 12:24:30

🦄个人主页:修修修也

🎏所属专栏: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:Y

int 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 5

int 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:Y

int 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*X

int 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.coloradmin.cn/o/1064424.html

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

相关文章

【短文】在Linux中怎么查看文件信息

2023年10月6日&#xff0c;周五晚上 ls -l filename 通过这条命令可以简略地查看文件信息 stat filename 通过这条命令可以详细地查看文件信息

NFS 原理和配置

NFS 原理介绍 NFS network file system 网络文件系统 基于RPC协议&#xff0c;RPC remote procedure call 。 RPC存在的意义在于解决NFS服务端和客户端通信多端口并且端口不固定的问题。因为NFS的服务端和客户端通信的时候&#xff0c;并不是只有一个端口&#xff…

【云计算网络安全】DDoS 缓解解析:DDoS 攻击缓解策略、选择最佳提供商和关键考虑因素

文章目录 一、前言二、什么是 DDoS 缓解三、DDoS 缓解阶段四、如何选择 DDoS 缓解提供商4.1 网络容量4.2 处理能力4.3 可扩展性4.4 灵活性4.5 可靠性4.6 其他考虑因素4.6.1 定价4.6.2 所专注的方向 文末送书《数据要素安全流通》本书编撰背景本书亮点本书主要内容 一、前言 云…

电脑提示MSVCP100.dll丢失错误怎么解决?分享四个解决方法帮你搞定

在平时我们使用电脑中&#xff0c;经常会遇到各种问题&#xff0c;比如msvcp100.dll文件丢失&#xff0c;那这个msvcp100.dll文件丢失需要怎么修复解决呢&#xff1f;和msvcp100.dll为什么会丢失呢&#xff0c;下面我一点点为大家解答与介绍解决msvcp100.dll丢失问题的方法。 一…

什么,这年头还有人不知道404

写在前面 哥&#xff0c;来帮我看看&#xff0c;这个请求怎么404了&#xff0c;明明接口路径是对的啊&#xff01;一个下午&#xff0c;组里的小哥突然让我帮忙看这个问题&#xff0c;我不禁一惊&#xff0c;啥&#xff0c;这年头了还有人搞不定404&#xff0c;如有还有&#…

【二】spring boot-设计思想

spring boot-设计思想 简介&#xff1a;现在越来越多的人开始分析spring boot源码&#xff0c;拿到项目之后就有点无从下手了&#xff0c;这里介绍一下springboot源码的项目结构 一、项目结构 从上图可以看到&#xff0c;源码分为两个模块&#xff1a; spring-boot-project&a…

最强的电脑/手机/汽车/机器人芯片-2023

车规级芯片、手机芯片、电脑芯片比较_汽车芯片和电脑芯片的区别-CSDN博客 全文资源来源网络。 电脑&#xff1a; 图片引用。 CPU 基准测试性能层次结构根据性能对当前和上一代英特尔和 AMD 处理器进行排名&#xff0c;包括所有最适合游戏的 CPU。在 CPU 排名图表和表格下方&…

2023年【安全员-A证】报名考试及安全员-A证免费试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 安全员-A证报名考试考前必练&#xff01;安全生产模拟考试一点通每个月更新安全员-A证免费试题题目及答案&#xff01;多做几遍&#xff0c;其实通过安全员-A证作业考试题库很简单。 1、【多选题】2014年2月&#xff…

【亲测有效】C盘容量满了,给C盘扩容!!!

前言 相信有很多小伙伴用自己电脑的时候明明不往C盘装东西&#xff0c;但是C盘还是慢慢的变红了&#xff0c;我也是因为C盘满了而备受困扰。又不知道如何解决或者怕自己鼓捣着磁盘数据没了。闲来无事&#xff0c;我查了一些资料&#xff0c;终于将我的C盘容量扩充了且数据保存…

k8s-9 ingress-nginx

nodeport 默认端口 nodeport默认端口是30000-32767&#xff0c;超出会报错 添加如下参数&#xff0c;端口范围可以自定义 externalname ingress-nginx 通过一个外部的vip 地址 访问到集群内的多个service 一种全局的、为了代理不同后端 Service 而设置的负载均衡服务&…

FLASH模拟EEPROM

STM32本身没有自带EEPROM&#xff0c;但是STM32具有IAP&#xff08;在应用编程&#xff09;功能&#xff0c;所以可以把它的FLASH当做EEPROM来使用。 STM32 FLASH简介 不同型号的STM32&#xff0c;其FLASH容量也有所不同&#xff0c;最大的达到1024K字节。 MiniSTM32开发板选…

打破思维局限性,产品背景、需求、功能实现逻辑手拿把掐!

在一个完整的测试流程中&#xff0c;测试用例是很核心的一个产出物。一份优秀的测试用例&#xff0c;能确保软件产品质量的可控。 但由于每个人思维局限性&#xff0c;对产品背景、需求、功能实现逻辑等理解深度不一致&#xff0c;编写的测试用例或多或少存在一些遗漏点&#…

C/C++学习 -- SHA-256算法

SHA-256算法概述 SHA-256代表"Secure Hash Algorithm 256-bit"&#xff0c;是一种安全的哈希算法&#xff0c;输出固定长度的256位&#xff08;32字节&#xff09;哈希值。SHA-256被广泛用于加密、数字签名、密码学以及区块链等领域&#xff0c;因为它提供了高度的安…

小程序如何关联视频号小店,实现商品同步

​随着短视频平台的兴起&#xff0c;视频号小店成为了很多商家推广产品和服务的新渠道。下面介绍如何将小程序与视频号小店关联起来&#xff0c;实现商品的同步。 1. 关联视频号小店。在小程序管理员后台->营销管理->视频号小店页面&#xff0c;点击双向箭头&#xff0c…

STM32+USB3300复位枚举异常的问题

关键字&#xff1a;STM32F4&#xff0c;STM32H7&#xff0c;USB3300&#xff0c;USBHS&#xff0c;Reset复位 F4和H7用的都是DWC2的USBIP&#xff0c;我的板子上3300单片机工作的很好&#xff0c;插入枚举一切正常&#xff0c;但是设备收到上位机的复位命令后&#xff0c;单片…

【Java 进阶篇】使用 JDBCTemplate 执行 DML 语句详解

JDBCTemplate 是 Spring 框架中的一个核心模块&#xff0c;用于简化 JDBC 编程&#xff0c;使数据库操作更加便捷和高效。在本文中&#xff0c;我们将重点介绍如何使用 JDBCTemplate 执行 DML&#xff08;Data Manipulation Language&#xff09;语句&#xff0c;包括插入、更新…

面试题:你是如何计划和组织一个大型的软件测试项目的?

今天我们讲个软件测试的面试问题&#xff1a;你是如何计划和组织一个大型的软件测试项目的&#xff1f; 这种题目&#xff0c;就是看你的流程梳理&#xff0c;一定要在回答的步骤前面加上1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;自己就能很清晰&#xff0c;面试…

程序员如何从容地面对裁员?我有6个小建议

2023年3月&#xff0c;世界银行发布了一份题为《长期下行的增长前景&#xff1a;趋势、期望和政策》的报告&#xff0c;首次全面评估了未来全球的经济发展趋势。报告描述的趋势令人担忧&#xff1a;推动过去三十年进步和繁荣的所有经济力量几乎都在消退 2022年至2030年的全球潜…

Windows系统无法激活Python虚拟环境的解决方案:无法加载文件 ,因为在此系统上禁止运行脚本。

原文链接&#xff1a;Windows系统无法激活Python虚拟环境的解决方案 我的个人博客//推广一下w 情况描述 在Windows系统终端激活Python虚拟环境时可能出现以下报错&#xff08;假设你的虚拟环境名为“.venv”&#xff09;&#xff1a; PS (yourpath\yourProj)> .\.venv\S…

CTFHUB SSRF

目录 web351 ​编辑 web352 web353 web354 sudo.cc 代表 127 web355 host长度 web356 web357 DNS 重定向 web358 bypass web359 mysql ssrf web360 web351 POST查看 flag.php即可 web352 <?php error_reporting(0); highlight_file(__FILE__); $url$_…