#define 宏定义看这一篇文章就够了
前言:在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
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 定义符号和宏时,需要涉及几个步骤
- 在调用宏时,首先对参数进行检查,看看是否包含任何由 #define 定义的符号。如果是,它们首先被替换
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换
- 最后,再次对结果文件进行扫描,看看它是否包含任何由 #define 定义的符号。如果是,就重复上述处理过程
另外需注意:
- 宏参数和 #define 定义中可以出现其他 #define 定义的符号。但是对于宏,不能出现递归
- 当预处理器搜索 #define 定义的符号的时候,字符串常量的内容并不被搜索
⑥ 带副作用的宏参数
x+1;//不带副作用
x++;//带有副作用
#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))
- 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多, 所以宏比函数在程序的规模和速度方面更胜一筹
- 更为重要的是函数的参数必须声明为特定的类型, 所以函数只能在类型合适的表达式上使用,反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型,宏是类型无关的
宏的缺点:
- 每次使用宏的时候,一份宏定义的代码将插入到程序中,除非宏比较短,否则可能大幅度增加程序 的长度
- 宏是没法调试的
- 宏由于类型无关,也就不够严谨
- 宏可能会带来运算符优先级的问题,导致程容易出现错
总结
属 性 | #define定义宏 | 函数 |
代 码 长 度 | 每次使用时,宏代码都会被插入到程序中,除了非常 小的宏之外,程序的长度会大幅度增长 | 函数代码只出现于一个地方;每 次使用这个函数时,都调用那个 地方的同一份代码 |
执 行 速 度 | 更快 | 存在函数的调用和返回的额外开销,所以相对慢一些 |
操 作 符 优 先 级 | 宏参数的求值是在所有周围表达式的上下文环境里, 除非加上括号,否则邻近操作符的优先级可能会产生 不可预料的后果,所以建议宏在书写的时候多些括号 | 函数参数只在函数调用的时候求值一次,它的结果值传递给函数,表达式的求值结果更容预 测 |
带 有 副 作 用 的 参 数 | 参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果 | 函数参数只在传参的时候求值一次,结果更容易控制 |
参 数 类 型 | 宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型 | 函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的 |
调 试 | 宏是不方便调试的 | 函数是可以逐语句调试的 |
递 归 | 宏是不能递归的 | 函数是可以递归的 |
本次分享就到此为止了,如有不不同观点,欢迎评论区讨论交流,感谢您的支持,码文不易,给个三连支持吧
相关文章:

#define 宏定义看这一篇文章就够了
前言:在c/c学习的过程中,宏定义(#define)是作为初学者学习到的为数不多的预处理指令,在学习的时候我们被告知他可以帮助我们更高效的写程序,可以增加程序的可读性,但宏定义(#define&…...

LeetCode算法栈—验证图书取出顺序
验证图书取出顺序 目录 验证图书取出顺序 题解: 代码: 运行结果: 验证图书取出顺序 现在图书馆有一堆图书需要放入书架,并且图书馆的书架是一种特殊的数据结构,只能按照 一定 的顺序 放入 和 拿取 书籍。 给定一个…...

PAM从入门到精通(十八)
接前一篇文章:PAM从入门到精通(十七) 本文参考: 《The Linux-PAM Application Developers Guide》 PAM 的应用开发和内部实现源码分析 先再来重温一下PAM系统架构: 更加形象的形式: 六、整体流程示例 2.…...
【区间 DP】热门区间 DP 运用题
题目描述 这是 LeetCode 上的 「312. 戳气球」 ,难度为 「困难」。 Tag : 「区间 DP」、「动态规划」 有 n 个气球,编号为 0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。 现在要求你戳破所有的气球。戳破第 i …...

正则表达式,日期选择器时间限制,报错原因
目录 一、正则表达式 1、表达式含义 2、书写表达式 二、时间限制 1、原始日期选择器改造 2、禁止选择未来时间 3、从...到...两个日期选择器的时间限制 三、Uncaught (in promise) Error报错 一、正则表达式 1、表达式含义 (1)/^([a-zA-Z0-9_.…...
YOLOv7 改进原创 HFAMPAN 结构,信息高阶特征对齐融合和注入,全局融合多级特征,将全局信息注入更高级别
💡本篇内容:YOLOv7 改进原创 HFAMPAN 结构,信息高阶特征对齐融合和注入,全局融合多级特征,将全局信息注入更高级别 💡🚀🚀🚀本博客 改进源代码改进 适用于 YOLOv7 按步骤操作运行改进后的代码即可 💡本文提出改进 原创 方式:二次创新,YOLOv7 专属 论文理…...

django建站过程(1)
django建站过程(1) 使用pycharm创建过程运行项目创建数据库创建超级用户登录生成的后台:界面本地化 准备以django,bootstrap来做一个过程记录,文章主要阐述过程的细节。 使用pycharm创建过程 创建项目“schoolapps”,…...

使用 Typhoeus 和 Ruby 编写的爬虫程序
以下是一个使用 Typhoeus 和 Ruby 编写的爬虫程序,用于爬取 ,同时使用了 jshk.com.cn/get_proxy 这段代码获取代理: #!/usr/bin/env rubyrequire typhoeus require jsondef get_proxyurl "https://www.duoip.cn/get_proxy"respon…...

Git 安装和基础命令、IDEA 基础操作
目录 总结命令:1、安装:1、安装2、配置环境变量: 2、Git操作:1、初始化:1、姓名邮箱:2、初始化仓库:3、工作区和暂存区分析 2、提交文件3、查看版本库状态4、安装小乌龟git不显示图标 5、查看提…...

做一个最新版的淘宝客返利程序源码有多难?
我们都知道淘宝客返利程序成为了很多人的创业和赚钱的工具。这种程序允许通过推广淘宝商品来获得佣金。然而,你知道构建这样一个淘宝客返利程序有多难吗?今天我们就从最基本的API说起,现在我将介绍构建一个最新版淘宝客返利程序所需的关键API…...
day5:Node.js 第三方库
day5:Node.js 第三方库 文章目录 day5:Node.js 第三方库使用 Express.js 构建 Web 应用安装 Express第一个 Express 框架实例第二个 Express 框架实例Node.js 连接 MySQL查询数据插入数据更新数据删除数据使用 Express.js 构建 Web 应用 Express框架是Node.js生态系统中的一…...

如何正确停止线程?为什么 volatile 标记位的停止方法是错误的?
Java全能学习面试指南:https://javaxiaobear.cn 今天我们主要学习如何正确停止一个线程?以及为什么用 volatile 标记位的停止方法是错误的? 首先,我们来复习如何启动一个线程,想要启动线程需要调用 Thread 类的 start…...

pytorch nn.Embedding 读取gensim训练好的词/字向量(有例子)
最近在跑深度学习模型,发现Embedding随机性太强导致模型结果有出入,因此考虑固定初始随机向量,既提前训练好词/字向量,不多说上代码!! 1、利用gensim训练字向量(词向量自行修改) #…...

2.1.1BFS中的Flood Fill和最短路模型
1.池塘计数 农夫约翰有一片 N ∗ M N∗M N∗M 的矩形土地。 最近,由于降雨的原因,部分土地被水淹没了。 现在用一个字符矩阵来表示他的土地。 每个单元格内,如果包含雨水,则用”W”表示,如果不含雨水,…...
Mysql 新增更新、删除新增、忽略
当主键或唯一键冲突时,Mysql可以进行更新、删除新增、忽略插入等操作。 1.更新 当主键或唯一键冲突时,可以指定更新内容。 INSERT INTO table_name (column_name, column_name, column_name) VALUES (column_value, column_value,column_value) ON DUPL…...
Node-模块系统的用法
题记 node.js模块系统的用法,以下是具体操作过程和代码 为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统。 模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的。 一个 Node.js 文件就是一个模块,这…...
XSS攻击(1), 测试XSS漏洞, 获取cookie
XSS漏洞, 测试XSS漏洞, 获取cookie 一, 概念: XSS(Cross-Site Scripting), 跨站攻击脚本, XSS漏洞发生在前端, 依赖于浏览器的解析引擎, 让前端执行攻击代码. XSS其实也算注入类的攻击, XSS代码注入需要有JavaScript编程基础. 二, 目的: XSS(跨站脚本࿰…...

linux任务优先级
这篇笔记记录了linux任务(指线程而非进程)优先级相关的概念,以及用户态可以用来操作这些优先级的系统调用。 基本概念 调度策略 linux内核中的调度器为任务定义了调度策略,也叫调度类,每个任务同一时刻都有唯一的调…...

JVM内存模型概述
这里主要分为五大块,分别是:本地方法栈、方法区、java堆、程序计数器和java栈。其中重点是方法区、java堆和java栈。 下面就把各个区域的性质总结一下:(说明,下面的只是结论,没有详细的对各个内存块进行详细…...

【JavaEE】CAS -- 多线程篇(7)
CAS 1. 什么是 CAS2. CAS 伪代码3. CAS 是怎么实现的4. CAS的应用4.1 实现原子类4.2 实现自旋锁 5. CAS 的 ABA 问题 1. 什么是 CAS CAS: 全称Compare and swap,字面意思:”比较并交换“能够比较和交换 某个寄存器中的值和内存中的值, 看是否相等, 如果相等, 则把另…...

51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...

3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...

高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...

自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...