#define 宏定义看这一篇文章就够了

news2024/12/23 14:57:58

        前言:在c/c++学习的过程中,宏定义(#define)是作为初学者学习到的为数不多的预处理指令,在学习的时候我们被告知他可以帮助我们更高效的写程序,可以增加程序的可读性,但宏定义(#define)的功能还远远不如此,比如他还可以被当做函数一样使用,宏定义甚至能做许多函数做不到的事情,本文笔者就给大家详细解析宏定义背后的奥秘世界

目录

① 宏定义(#define)是什么

例外

② 常用宏定义用法 

③ 续行操作

④ #define 定义宏

宏内部的隐患

宏外部的隐患

⑤ #define 替换规则

⑥ 带副作用的宏参数

⑦ 宏与函数的相似

# 的使用

## 的使用

⑧ 宏与函数的区别

宏的缺点:

总结


① 宏定义(#define)是什么

        #define 可以将一对文本进行替换,在编译器读到需要被替换的文本的时候,会将这些文本全部替换成我们给定的文本,这样说还是有点抽象,我们拿一段示例在演示说明

#define A 100
#define B 200
int main()
{
	int C = 0;
	C = A + B;
	printf("C = %d\n", C);

	return 0;
}

        在这里,我们将 “A”和“B” 分别使用宏定义,定义为 100和200,在以后的语句中,一旦编译器读取到 “A”和“B” 就会直接将该位置的 “A”和“B” 替换为对应的数字文本,因此 “C” 的最后值为 300

例外

        但需要注意,这里的替换文本是不包含在字符串内部的,也就是说,我们要替换的文本必须的一段完整的,他不能是一段字符串的一部分,我们还是用示例来说明一下

#define A 100
#define B 200
int main()
{

	int C = 0;
	C = A + B;
	printf("C = %d\n", C);

	int ABC = 0;
	char arr[] = "ABD";
	printf("ABC=%d\n", ABC);
	printf("arr=%s\n", arr);

	return 0;
}

        我们在刚才的代码后再加一段,定义一个整形变量 ABC,定义一个字符数组 arr (内容是“ABC”) ,我们试着运行一下会发现,对于一个完整的变量或者字符串,假如他的内部的一部分字符是我们宏定义的对象的话,他是不会进行文本替换操作的

        正如这里的 ABC 变量,他并没有被替换为 “100200C”,后面的字符数组也是,它并没有被替换为 “100200C”


② 常用宏定义用法 

        宏定义的内容是多种多样的,可以是数字可以是字符,可以是变量,可以是遇见,也可以是函数,以下给出一些宏定义使用方法和示例

#define MAX 1000               // MAX 的大小定义为 1000
#define reg register           //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)     //用更形象的符号来替换一种实现
#define CASE break;case        //在写case语句的时候自动把 break写上
  • 前面俩种就是很简单的文本替换,在前文中已经进行了讲解,这里就不再继续赘述
  • 对于第三种,一般在实际中没有什么意义,当编译器读取到 do_forever 的时候,就直接替换为 for(;;) 相当于写了个一直不会停止的 for 循环,程序就会进入死循环

我们重点来看最后一个,我们先写出一个普通的 switch语句:

int main()
{
	int i = 0;
	scanf("%d", &i);
	switch (i)
	{
	case 1:
		printf("%d\n", i);
		break;
	case 2:
		printf("%d\n", i);
		break;
	case 3:
		printf("%d\n", i);
		break;
	case 4:
		printf("%d\n", i);
		break;
	}
	return 0;
}

在加入宏定义后

#define CASE break;case        //在写case语句的时候自动把 break写上
int main()
{
	int i = 0;
	scanf("%d", &i);
	switch (i)
	{
	case 1:
		printf("%d\n", i);
	CASE 2:
		printf("%d\n", i);
	CASE 3:
		printf("%d\n", i);
	CASE 4:
		printf("%d\n", i);
	}
	return 0;
}

         每一个大写的 CASE 语句都相当于为上一个 case 自动补写了个 break,因此整个程序除了第一个 case 以外其余的全部用宏定义进行替换,我们就可以省去写 break 的步骤,但是达到同样的目的


③ 续行操作

        我们知道,我们写的宏定义(#define)是一行一行的,前面是宏定义,中间是被替换的文本,最后是替换后的文本,但假如我们写了个宏定义,非常的长,一行根本写不下怎么办呢,这就需要使用续行操作符了

// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                          date:%s\ttime:%s\n" ,\
                          __FILE__,__LINE__ ,  \
                          __DATE__,__TIME__ )

④ #define 定义宏

#define 机制包括了一个规定,允许吧参数替换到文本中,这种实现通常被称为宏(macro)定义宏(define macro),一下是宏的申明方式:

#define name (parament-list) stuff

其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在 stuff 

注意:
  • 参数列表的左括号必须与name紧邻
  • 如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分

宏内部的隐患

 以下面的代码举例:

#define SQUARE( x ) x * x
        这个宏接收一个参数 x,假如代码块中写有  SQUARE ( 5 ); 的话,当编译器读到的时候,预处理器就会替换为 5*5 , 这给我们的感觉就和函数一样,你给定一个值,他给你返回一个值 ,但是这样的操作也会带来部分的问题,我们给定以下代码,大家可以思考判断会输出什么
int a = 5;
printf("%d\n" ,SQUARE( a + 1) );

你觉得会是何种输出结果?

  • A. 36
  • B. 11

结果非常的出人意料啊,按道理来说我们计算 5+1 的平方应该是 36 啊,为什么会输出 11 呢? 

        我们还是从宏定义的本质上来看,宏定义是替换文本,那我们试着替换一下,替换文本时,参数 x 被替换成 a + 1,所以这条语句实际上变成了:

printf ("%d\n",a + 1 * a + 1 ) 

因为乘号的优先级更大,所以运行的结果就是 5+1*5+1 ,也就是 11  

其实要避免这样的问题也很简单,我们在宏定义内容中加上括号就好了

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

 我们再试着运行一下:

宏外部的隐患

刚才我们出现的问题是来自于宏定义内部的问题,那如果是外部呢?我们还是给出一个宏定义:

#define DOUBLE(x) (x) + (x)

        吸取了刚才的教训,我们提前在内容中加上了括号,我们试着运行下面的代码,看看会输出什么样的结果

int a = 5;
printf("%d\n" ,10 * DOUBLE(a));

大家可以思考思考会是何种输出:

  • A. 100
  • B. 55

我们试着运行一下,结果还是和我们预期的不同:

 

对于这样的情况,我们给整个宏定义一个括号就可以解决问题了

#define DOUBLE(x) ((x) + (x))


⑤ #define 替换规则

通过以上的学习了解,我们可以大概总结如下:

在程序中扩展 #define 定义符号和宏时,需要涉及几个步骤
  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由 #define 定义的符号。如果是,它们首先被替换
  2.  替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由 #define 定义的符号。如果是,就重复上述处理过程

另外需注意:

  1. 宏参数和 #define 定义中可以出现其他 #define 定义的符号。但是对于宏,不能出现递归
  2. 当预处理器搜索 #define 定义的符号的时候,字符串常量的内容并不被搜索 

⑥ 带副作用的宏参数

        当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能 出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果
x+1;//不带副作用
x++;//带有副作用
MAX 宏可以证明具有副作用的参数所引起的问题
#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);//输出的结果是什么?

我们尝试替换:

z = ( (x++) > (y++) ? (x++) : (y++));
所以输出的结果是:
x=6 y=10 z=9

⑦ 宏与函数的相似

        经过上述的讲解后,笔者相信大家对宏定义应该都有了很深刻的理解,接下里我们来探讨探讨宏定义和函数的关系,如下面的代码,我们能给宏传参,能输出结果,我们可以很明确的感受到一种函数的感觉

#define PRINT(FORMAT, VALUE)\
		printf("the value is "FORMAT"\n", VALUE);
	PRINT("%d", 10);

# 的使用

 我们这里还有更高级的用法:使用 “#” 可以将宏的参数变为对应的字符串

#define PRINT(FORMAT, VALUE)\
		printf("the value of " #VALUE " is "FORMAT "\n", VALUE);
	int i = 10;
	PRINT("%d", i+3);

## 的使用

## 可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符
#define ADD_TO_SUM(num, value)\
		sum##num += value;
ADD_TO_SUM(5, 10);//作用是:给sum5增加10.

 注意:这样的连接必须产生一个合法的标识符,否则其结果就是未定义的


⑧ 宏与函数的区别

宏通常被应用于执行简单的运算,比如在两个数中找出较大的一个:

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

宏的缺点:

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中,除非宏比较短,否则可能大幅度增加程序 的长度
  2. 宏是没法调试的
  3. 宏由于类型无关,也就不够严谨
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错

总结

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

 




本次分享就到此为止了,如有不不同观点,欢迎评论区讨论交流,感谢您的支持,码文不易,给个三连支持吧

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

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

相关文章

电脑出现xinput1_3.dll的错误提示怎么办?有什么办法可以解决

电脑如果缺失了xinput1_3.dll还是一件比较复杂的事情,那么电脑出现xinput1_3.dll的错误提示怎么办,又有什么办法可以解决xinput1_3.dll?今天我们就来聊聊xinput1_3.dll丢失的解决办法,来看看都有哪些办法可以解决吧。 一.常见的问…

[swift刷题模板] 树状数组(BIT/FenwickTree)

[TOC]([swift刷题模板] 树状数组(BIT/FenwickTree) ) 一、 算法&数据结构 1. 描述 [python刷题模板] 树状数组 二、 模板代码 1. 单点赋值(增加),区间求和(PURQ) 例题: 307. 区域和检索 - 数组可修改 class BIT {var c: [Int]var n: Int init(_ n: Int){c…

13 Multi-Head Self-Attention(从空间角度解释为什么做多头)

博客配套视频链接: https://space.bilibili.com/383551518?spm_id_from=333.1007.0.0 b 站直接看 配套 github 链接:https://github.com/nickchen121/Pre-training-language-model 配套博客链接:https://www.cnblogs.com/nickchen121/p/15105048.html 上节课回顾 0:40 At…

java内部类学习总结/lambda表达式总结

使用内部类的情况 此时如果不使用内部类,下图会出现new 了两次Outer的情况 如果只想new 一次Outer 代码会很繁琐(如下),所以内部类可以很方便的访问外部类的一些私有属性,外部类也可方便访问内部类的私有属性 内部类说明 如果内部类没有追加 static 之前要想获取内部类的对象必…

Origami Studio for Mac:塑造未来,掌握原型设计之巅

在当今高度竞争的设计领域,原型设计的重要性不言而喻。它不仅是沟通想法,也是测试和改进设计的关键环节。而现在,一款强大的原型设计工具——Origami Studio for Mac,正在席卷设计界,以其独特的功能和卓越的性能&#…

【Javascript】基础数据类型

目录 基础数据类型 1.number 字面量声明 数字对象方式声明 整数判断 指定返回小数位数 NaN-表示非数字值 浮点精度 解决误差 String 字面量声明 数字对象声明 连接运算符 获取长度 大小写转换 转换成大写 转换成小写 ​编辑 移除空白 获取单字符 ​编辑 截…

循环队列----数据结构

缘由 在队列的顺序存储中,采用第二种出队的方式,将头指针 1 ,可以避免元素的移动,但是这样也出现了一个问题 "假溢出" ,如图: 当出现这种情况时:头指针和尾指针都指向了不可访问的地…

Qt窗体设计的布局

本文介绍Qt窗体的布局。 Qt窗体的布局分为手动布局和自动布局,手动布局即靠手工排布各控件的位置。而自动布局则是根据选择的布局类型自动按此类型排布各控件的位置,使用起来比较方便,本文主要介绍Qt的自动布局。 1.垂直布局 垂直布局就是…

[AUTOSAR][网络管理] 什么是BusOff? 如何实现它?

文章目录 一、BusOff检测机制(1)简介(2)目录介绍(3)软件实现逻辑代码运行流程如下:二、测试方法(1)物理测试三、示例代码(1) busoff_recovery.c(2) busoff_recovery.h一、BusOff检测机制 (1)简介 CAN控制器可以判断出错误的类型是总线上暂时的数据错误(如外部干扰等…

分布式锁 - 理论篇

一、为什么需要分布式锁 二、分布式锁实现 1.分布式锁演进 - 基本原理 我们可以同时去一个地方“占坑”,如果占到,就执行逻辑。否则就必须等待,直到释放锁。“占坑”可以去redis,可以去数据库,可以去任何大家都能访…

树状数组学习笔记

树状数组 树状数组的用途,主要是可以以 O ( log ⁡ n ) O(\log n) O(logn) 的时间复杂度维护前缀和。 对于树状数组的使用,我们开一个数组 c,c[x] 表示 [ x − lowbit ( x ) 1 , x ] [x-\text{lowbit}(x)1,x] [x−lowbit(x)1,x] 的区间和…

图像超分辨率超分辨率NeRF论文阅读

文章目录 前置知识图像超分辨率《High-resolution image reconstruction with latent diffusion models from human brain activity》【CVPR23】《Dynamic High-Pass Filtering and Multi-Spectral Attention for Image Super-Resolution》【ICCV21】《DiffBIR: Towards Blind …

视频的二维码怎么做?教你一个实用技巧操作

现在扫码看视频越来越常见,很多人都会用这种方式来展示内容,比如展示环境、产品说明、自拍录像等等,让别人能够更快的获取内容。举个例子,当我们从淘宝或者其他购物平台买东西时,现在咨询客服询问使用说明时&#xff0…

HTML超链接标签

目录 1&#xff1a;页面间的链接 1.1 文字跳转&#xff1a; 1.2 图片跳转 2&#xff1a;锚链接 1&#xff1a;页面间的链接 1.1 文字跳转&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><t…

webpack(五)热更新

webpack-dev-serve 开启本地服务器 现状&#xff1a;打包后将index.html文件通过open in liveServe打开&#xff0c;我们继续修改js、html文件内容&#xff0c;页面没有变化。 解决方法&#xff1a; 方法一&#xff1a; 在打包命令后面加上--watch,重新打包后我们的终端不会终…

类的继承简介

一、声明格式&#xff1a; class 子类名&#xff1a;继承方式(public private protected) 父类名{子类成员表} 二、继承过程&#xff1a; 吸取父类成员——>改造父类成员——>添加新成员 三、作用&#xff1a; 子类会继承父类中的方法(不包括构造和析构函数)与属性 …

进阶JAVA篇-深入了解 List 系列集合

目录 1.0 List 类的说明 1.1 List 类的常用方法 1.2 List 集合的遍历方式 2.0 ArrayList 集合的底层原理 2.1 从 ArrayList 集合的底层原理来了解具有该特性的原因&#xff1a; 2.2 ArrayList 集合的优缺点 3.0 LinkedList 集合的底层原理 3.1 从 LinkedList 集合的底层原理来了…

卷积神经网络CNN学习笔记-卷积计算Conv2D函数的理解

目录 1.全连接层存在的问题2.卷积运算3.填充(padding)3.1填充(padding)的意义 4.步幅(stride)5.三维数据的卷积运算6.结合方块思考7.批处理8.Conv2D函数解析9.conv2d代码9.1 stride19.2 stride2 参考文章 1.全连接层存在的问题 在全连接层中&#xff0c;相邻层的神经元全部连接…

多线程-进阶

常见的锁策略 乐观锁和悲观锁 这不是两把具体的锁, 这是两类锁 乐观锁: 预测锁的竞争不是很激烈 悲观锁: 预测锁的竞争会很激烈 乐观和悲观说的都不是绝对的, 唯一的区分就是看预测锁竞争激烈程度的结论, 这两种锁的背后工作是截然不同的, 轻量级锁和重量级锁 轻量级锁加锁解…

浅谈人工智能视频分析技术的原理及行业场景应用

人工智能视频分析技术是利用计算机视觉、模式识别和深度学习算法等技术&#xff0c;对视频数据进行自动化处理和分析的过程。其基本工作原理包括以下几个步骤&#xff1a; 视频采集&#xff1a;通过摄像头或其他视频设备获取源视频数据。 视频预处理&#xff1a;对视频进行去噪…