C语言#define定义宏

目录

一、什么是宏以及宏的声明方式

1.宏常量:

2.宏函数:

二、宏的替换原则

三、宏设计的易犯错误

 ERROR1:尾部加分号(当然有些特定需要加了分号,这里说明一般情况)

ERROR2:宏函数定义时,没有保证其参数在表达式中的独立性,以及表达式的独立性

 ERROR3:带副作用的宏参数

四、#,##

1.使用# ,把一个宏参数变成对应的字符串

2.使用##,允许宏定义从分离的文本片段创建标识符

五、宏函数特性分析


前言:   

        宏,每个C语言学习者绕不开的话题,其使用简便又十分易错,稍不注意甚至处处小心仍然容易写出BUG。那么宏是什么,应该如何正确使用(减小使用错误概率)?今天让我们来一探究竟。

一、什么是宏以及宏的声明方式

        #define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)

        宏的声明方式:

1.宏常量:

#define 宏常量名 宏常量值 ------- 例:#define  MAX  1000

说明:

       a.宏常量名和普通常量名没什么区别,不过习惯上使用大写字母表示

       b.语句不同成分之间用空格隔开,末尾不需要加分号

2.宏函数:

#define 宏函数名(参数列表) 宏函数表达式  ------ 例: #define  ADD  ( x, y )  ( ( x ) + ( y ) )

ps.上述函数表达式看起来很别扭,但是这是比较安全的写法,下文会进行解释。

说明:

        a.宏函数名和普通变量名没什么区别,不过习惯上使用大写字母表示

        b.语句不同成分之间用空格隔开,末尾不需要加分号

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

二、宏的替换原则

        我们在设计宏和使用宏时,往往因为对宏的替换原则不够了解而出现错误,而宏的替换总结起来就是两个字——“生硬”。

        宏是在编译的阶段进行的替换,编译器会对代码进行检查,对使用了宏常量名和宏函数名的代码段将对应的常量值或函数表达式直接在原位置处嵌入式替换。

例:

#define MAX 1000
#define ADD( x, y) ( ( x ) + ( y ) )int main()
{
// 替换前printf("%d",MAX);printf("%d",ADD( 1, 2));
// 替换后printf("%d",1000);printf("%d",( ( 1 ) + ( 2 ) ));return 0;
}

        这就如同我们进行覆盖式 ctrl + c/v 一样,如此这般确实显得编译器很生硬,但这也是没有办法,毕竟语法规定,同时编译器也不能自作主张的替换,这就要求我们编程者根据特性来好好设计宏了 。       

三、宏设计的易犯错误

        了解了宏的替换原则,想必对于设计宏犯错误的原因也大概知道了,就是由于生硬的替换而导致逻辑或者语义甚至语法的错误出现。

 ERROR1:尾部加分号(当然有些特定需要加了分号,这里说明一般情况)

宏替换后, max = MAX;就变成 max = 1000;; ,这也就导致了在if else语句之间出现了一个空语句,else找不到if而落单了,这很明显是有问题的。

宏替换后,printf("%d",MAX) ;就变成了 printf("%d",1000;) ; ,这一个明眼人都能看出明显问题。

ERROR2:宏函数定义时,没有保证其参数在表达式中的独立性,以及表达式的独立性

     这里我们设计一下加法宏函数

这样的结果会是多少呢?想必根据上文提到的替换规则,应该很清楚,发生替换后,变为

5 * 2 + 3 = 13

回答正确!

那如果这样呢?还是13吗?

这时,就昏头了,是先计算宏参数值呢?还是先替换?测试瞧瞧

这是 5 * 1 + 1 + 2 + 1的结果,看来是先直接替换无疑了,这也很体现宏的“生硬”

        宏函是仿照函数的,应该和函数基本性质相同才对,而对于函数,函数参数传参时应该先计算后传入,对于函数表达式更是一个整体,全算完了才进行外部动作。

所以对于宏函数的函数表达式,我们应该用括号将参数以及函数表达式的独立性体现出来,于是我们这样定义: #define  ADD(x,y)  (( x )+( y ))

  

 ERROR3:带副作用的宏参数

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

这里的副作用通常是指对自身进行改变的作用。如:x + 1 与 x++

我们进行替换

结果

在进行判断后,x,y自增,选择结束后y又进行自增,所以这即便给每个参数都确保的独立性,也无法保证其正确。

四、#,##

1.使用# ,把一个宏参数变成对应的字符串

#对于宏,有这么一个技巧,可以把一个宏参数变成对应的字符串:

PRINT("%d", i + 3) 变为  printf("the value of "i + 3" is"“%d” "\n", 1 + 3) 

ps.这里还涉及printf字符串拼接的特性printf("haha""nihao") -> printf("hahanihao")

2.使用##,允许宏定义从分离的文本片段创建标识符

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

五、宏函数特性分析

        通过上文,应该不会有人还觉得宏函数那么好干嘛还要使用普通函数了吧。

a.调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。(创建函数栈桢有内存消耗)所以宏比函数在程序的规模和速度方面更胜一筹

b.函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏可以适用于整形、长整型、浮点型等可以用于来比较的类型。宏是类型无关的。但也意味着宏没有类型检查,这在一些时候是不安全的体现。

c. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度,增加编译工作量。
d.宏在编译阶段就进行替换,是没法调试的

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

        综上宏函数适合短小计算,可以替代小型函数,但大型函数也是无能为力,对于类型检查有要求的,可以使用enum枚举常量来替代。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第33 章 - ES 实战篇 - MySQL 与 Elasticsearch 的一致性问题

思维导图 0. 前言 MySQL 与 Elasticsearch 一致性问题是老生常谈了。网上有太多关于这方面的文章了,但是千篇一律,看了跟没看没有太大区别。 在生产中,我们往往会通过 DTS 工具将 binlog 导入到 Kafka,再通过 Kafka 消费 binlog&…

Gitlab-Runner配置

原理 Gitlab-Runner是一个非常强大的CI/CD工具。它可以帮助我们自动化执行各种任务,如构建、测试和部署等。Gitlab-Runner和Gitlab通过API通信,接收作业并提交到执行队列,Gitlab-Runner从队列中获取作业,并允许在不同环境下进行作…

STM32第6章、WWDG

一、简介 WWDG:全称Window watchdog,即窗口看门狗,本质上是一个能产生系统复位信号和提前唤醒中断的计数器。 特性: 是一个递减计数器。 看门狗被激活后, 当递减计数器值从 0x40减到0x3F时会产生复位(即T6位…

【Qt】事件、qt文件

目录 Qt事件 QEvent QMouseEvent QWheelEvent QKeyEvent QTimerEvent Qt文件 QFile QFileInfo Qt事件 在Qt中用一个对象表示一个事件,这些事件对象都继承自抽象类QEvent。事件和信号的目的是一样的,都是为了响应用户的操作。有两种产生事件的方…

Jenkins触发器--在其他项目执行后构建

前言: jenkins中有多种触发器可用,可以方便的控制构建的启动 这里简单介绍下项目后构建的配置方法 1. 解释: Build after other projects are built Set up a trigger so that when some other projects finish building, a new build is…

OpenStack 网络服务的插件架构

OpenStack 的网络服务具有灵活的插件架构,可支持多种不同类型的插件以满足不同的网络需求。以下是对 OpenStack 网络服务插件架构中一些常见插件类型的介绍: 一、SDN 插件 Neutron 与 SDN 的集成:在 OpenStack 网络服务里,SDN 插…

牛客网刷题 ——C语言初阶(6指针)——BC105 矩阵相等判定

1. 题目描述:BC105 矩阵相等判定 牛客网OJ题链接 描述: KiKi得到了两个n行m列的矩阵,他想知道两个矩阵是否相等,请你回答他。(当两个矩阵对应数组元素都相等时两个矩阵相等)。 示例1 输入: 2 2 1 2 3 4 1 2 3 4 输出…

SQLAlchemy

https://docs.sqlalchemy.org.cn/en/20/orm/quickstart.htmlhttps://docs.sqlalchemy.org.cn/en/20/orm/quickstart.html 声明模型 在这里,我们定义模块级构造,这些构造将构成我们从数据库中查询的结构。这种结构被称为 声明式映射,它同时定…

[SMARTFORMS] 导出SMARTFORMS表单数据

当我们配置好了Smartforms表单以后,如何在自开发的ALV程序报表中以PDF格式导出表单数据到电脑本地? 效果图 选择需要进行导出的采购凭证编号行数据,点击PDF格式导出按钮,弹出导出数据的信息窗口,点击"允许"…

seo泛目录(seo泛目录程序)

导言: 在搜索引擎优化(SEO)的领域中,泛目录程序被广泛应用于提升网站的可见性和排名。本文将深入探讨SEO泛目录程序的概念和作用,重点介绍它在网站优化中的重要性和优势,帮助读者了解SEO泛目录程序的工作原…

Trimble自动化激光监测支持历史遗产实现可持续发展【沪敖3D】

故事桥(Story Bridge)位于澳大利亚布里斯班,建造于1940年,全长777米,横跨布里斯班河,可载汽车、自行车和行人往返于布里斯班的北部和南部郊区。故事桥是澳大利亚最长的悬臂桥,是全世界两座手工建…

[人工智能自学] Python包学习-pandas

紧接上篇numpy的学习教程 本篇参考: Pandas 教程|菜鸟教程 官方教程 - 10分钟入门pandas joyful-pandas pandas中文教程 它建立在 NumPy 库的基础之上,提供了高效的数据结构和数据分析工具,使得在 Python 中进行数据操作变得更加容易和高效。…

【2024年华为OD机试】 (A卷,100分)- 二元组个数(Java JS PythonC/C++)

一、问题描述 以下是题目描述的 Markdown 格式: 题目描述 给定两个数组 a 和 b,若 a[i] b[j],则称 [i, j] 为一个二元组。求在给定的两个数组中,二元组的个数。 输入描述 第一行输入 m,表示第一个数组的长度。第二…

数据结构与算法之二叉树: LeetCode 543. 二叉树的直径 (Ts版)

二叉树的直径 https://leetcode.cn/problems/diameter-of-binary-tree/description/ 描述 给你一棵二叉树的根节点,返回该树的 直径 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 两节点之间路径的 长度 …

C# OpenCV机器视觉:OCR产品序列号识别

在一个看似平常却又暗藏玄机的工作日,阿明正坐在办公室里,对着堆积如山的文件唉声叹气。突然,电话铃声如炸雷般响起,吓得他差点从椅子上摔下来。原来是公司老板打来的紧急电话:“阿明啊,咱们刚生产出来的那…

【Powershell】Windows大法powershell好(二)

PowerShell基础(二) 声明:该笔记为up主 泷羽的课程笔记,本节链接指路。 警告:本教程仅作学习用途,若有用于非法行为的,概不负责。 1. powershell 执行外部命令 powershell也可以执行一些外部的…

JVM之垃圾回收器概述(续)的详细解析

ParNew(并行) Par 是 Parallel 并行的缩写,New 是只能处理的是新生代 并行垃圾收集器在串行垃圾收集器的基础之上做了改进,采用复制算法,将单线程改为了多线程进行垃圾回收,可以缩短垃圾回收的时间 对于其他的行为(…

WPF系列八:图形控件Path

简介 Path控件支持一种称为路径迷你语言(Path Mini-Language)的紧凑字符串格式,用于描述复杂的几何图形。这种语言通过一系列命令字母和坐标来定义路径上的点和线段,最终绘制出想要的图形。 绘制任意形状:可以用来绘…

基类指针指向派生类对象,基类指针的首地址永远指向子类从基类继承的基类首地址

文章目录 基类指针指向派生类对象&#xff0c;基类指针的首地址永远指向子类从基类继承的基类起始地址。代码代码2 基类指针指向派生类对象&#xff0c;基类指针的首地址永远指向子类从基类继承的基类起始地址。 代码 #include <iostream> using namespace std;class b…

《Spring Framework实战》3:概览

欢迎观看《Spring Framework实战》视频教程 Spring Framework 为基于现代 Java 的企业应用程序提供了全面的编程和配置模型 - 在任何类型的部署平台上。 Spring 的一个关键要素是应用程序级别的基础设施支持&#xff1a;Spring 专注于企业应用程序的 “管道”&#xff0c;以便…