【C语言】宏详解(上卷)

前言

紧接着预处理详解(上卷),接下来我们来讲宏(隶属于预处理详解系列)。

#define定义宏

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

所以,宏其实是有参数的,这是与我们之前的#define定义常量很不同的一点。

宏的声明方式

#define name(parameter-list) stuff

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

与函数不同的地方在于,宏的参数是没有类型的。

 宏运行把参数列表里的东西替换到内容(stuff)里。

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

演示:

比如我们现在实现一个宏,求平方。

SQUARE就是宏的名字,n是宏的参数,n*n是宏的内容。可以看到,参数出现在宏的内容中。

编译器对其预处理后是这样的:

#include<stdio.h>
#define SQUARE(n) n*nint main()
{int x = 0;scanf("%d", &x);int ret = x*x;//替换了printf("%d\n", ret);return 0;
}

理解:

宏和函数

 我们可以感受到,宏和函数很相似,如果写成函数是这样:

#include<stdio.h>
int square(int n)
{return n * n;
}int main()
{int x = 0;scanf("%d", &x);int ret = square(x);printf("%d\n", ret);return 0;
}

对于函数来说,要有函数名,参数,返回类型,函数体;对于宏来说,要有宏的名字,参数,宏的体(内容),不过没有参数类型、返回类型。

宏适合完成相对简单的任务。因为当任务复杂时,放进括号(函数的{})里会更方便观看。

宏与括号

现在,如果我们将参数改为5+1,会发生什么?

可以看到我们是得不到想要的结果36的,而是结果为11。 

这是因为它是这么替换的:

#include<stdio.h>
#define SQUARE(n) n*nint main()
{int ret = 5 + 1 * 5 + 1;printf("%d\n", ret);return 0;
}

直接替换,而不是算成6后再替换。

可以这样修改:

所以,在写宏的时候,不要吝啬括号。 

再举一个例子,下面的代码是经不起考验的:

实现一个宏,求一个数的2倍:

我们得不到想要的100,而是得到55。这是因为替换后是这样的:

#include<stdio.h>
#define DOUBLE(x) x+x
int main()
{/*int n = 0;scanf("%d", &n);*/int ret = 10 * 5 + 5;printf("%d\n", ret);return 0;
}

也是因为是直接替换。

这时应该这样改:

再次重申这个忠告:在写宏的时候一定不要吝啬括号。

用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符(前一个例子)或邻近操作符(后一个例子),产生不可预料的相互作用。

带有副作用的宏参数

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

举个例子说明什么是副作用:

#include<stdio.h>
int main()
{int a = 10;int b = a + 1;//b=11,a=10。无副作用int b = ++a;//b=11,a=11。有副作用return 0;
}

如果我们的宏的参数是带有副作用的呢?会发生什么呢?

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

演示:

#include<stdio.h>
#define MAX(x,y) ((x)>(y)?(x):(y))
//写一个宏,求两个数的较大值
int main()
{int a = 10;int b = 20;int m = MAX(a, b);printf("%d\n", m);return 0;
}

(x)>(y)?(x):(y)是如果(x)大于(y)表达式结果就为(x),否则表达式结果为(y)。

我们可以看到,宏的参数x、y都在宏体内出现了两次。如果现在我们传的是a++,b++,此时算出的m是多少呢?

这是替换后:

int m = ((a++)>(b++)?(a++):(b++));

怎么算呢?

从左向右依次计算,a++是后置++,先试用后++,所以(a++)表达式结果是10,(b++)是20,但是a和b都会++一次,因为10<20,后面的表达式(a++)不会执行,执行的是(b++),b此时已是21,先使用21再++,m得到的是21。真个表达式结束后a变成了11,b变成了22。

 如果写成函数,则是这样的:

a++ b++是先使用再++,所以传给函数形参的值是10和20,最后得到的m也是正确的结果。最后打印的a和b也是分别++一次的结果。

但因为宏使用的是替代到宏体内的方式,如果宏体内同一个参数出现多次,++也会出现多次。这种行为是非常危险的。

所以我们在写宏的时候,最好不要传带有副作用的参数,否则产生的结果可能不是我们希望的。

至此,上卷内容结束,祝阅读愉快,敬请期待下卷^_^

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

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

相关文章

AI如何创造情绪价值

随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;已经渗透到我们生活的方方面面。从智能家居到自动驾驶&#xff0c;从医疗辅助到金融服务&#xff0c;AI技术的身影无处不在。而如今&#xff0c;AI更是涉足了一个全新的领域——创造情绪价值。 AI已经能够处…

2024年【天津市安全员C证】免费试题及天津市安全员C证试题及解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 天津市安全员C证免费试题是安全生产模拟考试一点通生成的&#xff0c;天津市安全员C证证模拟考试题库是根据天津市安全员C证最新版教材汇编出天津市安全员C证仿真模拟考试。2024年【天津市安全员C证】免费试题及天津市…

分享美好,高清无阻 - 直播极简联网解决方案

1、需求背景 随着移动互联网、UGC模式和直播平台的发展&#xff0c;网络直播的门槛日益降低&#xff0c;越来越多的人希望成为直播的主角。基于物联网的户外直播无线联网解决方案应运而生&#xff0c;满足直播者的需求。 户外直播无线联网解决方案提供了无处不在的直播体验&a…

SpringBoot与MyBatis的快速整合(基于注解)

文章目录 创建Spring Boot项目配置数据库连接信息编写MyBatis Mapper接口使用XML文件编写SQL映射配置数据源切换引入Druid依赖配置Druid数据源 配置MyBatis支持事务管理 在使用Spring Boot创建新项目或新模块时&#xff0c;如果需要使用MyBatis来进行数据库操作&#xff0c;可以…

可变类型与不可变类型在命名空间中的一些场景

在Python中&#xff0c;变量空间的概念通常指的是变量在内存中的存储位置。可变类型和不可变类型在变量空间中的表现确实会导致一些常见的误解和错误。以下是一些具体的例子&#xff1a; ### 1. 可变类型作为函数参数 当你将可变类型&#xff08;如列表&#xff09;作为参数传递…

js的锚点

本文描述js的锚点解释及使用方法: 在Web开发中&#xff0c;锚点&#xff08;Anchor&#xff09;通常用于创建页面内的链接&#xff0c;允许用户点击链接直接跳转到同一页面的不同部分。JavaScript可以用来增强锚点的功能&#xff0c;例如实现平滑滚动或动态修改锚点行为。以下…

Python面试宝典:Python中与数据科学概念相关的面试笔试题(1000加面试笔试题助你轻松捕获大厂Offer)

Python面试宝典:1000加python面试题助你轻松捕获大厂Offer【第二部分:Python高级特性:第二十六章:Python与数据科学:第一节:数据科学】 第二十六章:Python与数据科学第一节:数据科学python中与数据科学概念相关的面试笔试题面试题1面试题2面试题3面试题4面试题5更多面试…

uniapp实现微信小程序调用云函数【vue2】

在uniapp中的vue 2框架中想要改变默认的目录结构&#xff0c;将装有云函数的文件夹在运行后一起复制到unpackage 文件下&#xff0c;主要用 copy-webpack-plugin 方法来实现&#xff0c;具体步骤如下&#xff1a; 一、创建一个vue 2 框架的uniapp 二、新建一个文件夹装云函数 …

Git【版本控制命令】

02 【本地库操作】 1.git的结构 2.Git 远程库——代码托管中心 2.1 git工作流程 代码托管中心用于维护 Git 的远程库。包括在局域网环境下搭建的 GitLab 服务器&#xff0c;以及在外网环境下的 GitHub 和 Gitee (码云)。 一般工作流程如下&#xff1a; 1&#xff0e;从远程…

轻松掌握系统概况,提升工作效率

作为 Linux 系统管理员,我们经常需要了解系统的基本状况,比如当前时间、系统版本、内核信息、CPU 型号、内存使用等等。但是每次手动执行各种命令来获取这些信息,无疑是一件非常繁琐的事情。 幸运的是,我们可以通过编写一个简单的 shell 脚本来一键获取这些系统信息。让我们一…

Java 环境配置 -- Java 语言的安装、配置、编译与运行

大家好&#xff0c;我是栗筝i&#xff0c;这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 002 篇文章&#xff0c;在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验&#xff0c;并希望进…

【iOS】内存泄漏检查及原因分析

目录 为什么要检测内存泄漏&#xff1f;什么是内存泄漏&#xff1f;内存泄漏排查方法1. 使用Zombie Objects2. 静态分析3. 动态分析方法定位修改Leaks界面分析Call Tree的四个选项&#xff1a; 内存泄漏原因分析1. Leaked Memory&#xff1a;应用程序未引用的、不能再次使用或释…

我的编程语言学习记录:一段不断探索的旅程

目录 我的编程语言学习记录&#xff1a;一段不断探索的旅程 1.引言 2.我的编程之旅开始 第一站&#xff1a;Python — 简洁之美 第二站&#xff1a;JavaScript — 网页的魔法 第三站&#xff1a;Java — 企业级的力量 3.学习过程中的挑战与克服 1.理解概念 3.记忆语法…

牛客网刷题 | BC118 N个数之和

目前主要分为三个专栏&#xff0c;后续还会添加&#xff1a; 专栏如下&#xff1a; C语言刷题解析 C语言系列文章 我的成长经历 感谢阅读&#xff01; 初来乍到&#xff0c;如有错误请指出&#xff0c;感谢&#xff01; 描述 输入数字N&#xf…

Servlet-01

文章目录 Servlet创建Servlet探究Servlet的生命周期 HttpServletWebServlet注解详解 重定向与请求转发ServletContextServletContext中的接口 HttpServletRequestHttpServletResponse状态码解释Cookie Servlet Q&#xff1a;它能做什么呢&#xff1f; A&#xff1a;我们可以通…

Hadoop3:MapReduce源码解读之Map阶段的数据输入过程整体概览(0)

一、MapReduce中数据流向 二、MapTask并行度 1、原理概览 数据块&#xff1a;Block是HDFS物理上把数据分成一块一块。数据块是HDFS存储数据单位。 数据切片&#xff1a;数据切片只是在逻辑上对输入进行分片&#xff0c;并不会在磁盘上将其切分成片进行存储。数据切片是MapRed…

XUbuntu24.04之ch9344(usb转串口芯片)安装驱动(二百四十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

11-Eureka-服务发现

11-Eureka-服务发现 1.在order-service完成服务拉取: 服务拉取是基于服务名称获取服务列表,然后再对服务列表做负载均衡 1.修改OrderService的代码,修改访问的url路径,用服务名称代替ip、端口: ​ String url = "http://userservice/user/" + order.getUser…

xshell远程无法链接上VM的centos7

1、现象如下&#xff0c; 2.解决办法&#xff1a;查证后发现这个默认的设置为vmnet0 3.参考文章&#xff1a;Xshell连接不上虚拟机centos7_centos7的nat模式可以ping通网络,但是用xshell连不上是什么原因-CSDN博客

linux内核获取未导出函数地址的两种方法

第一种 第一种是借助于kprobe机制&#xff0c;通过kprobe机制中会调用kallsyms_lookup_name函数并设置到kprobe结构体中返回的原理找到我们需要的函数地址 内核中调用逻辑简化代码如下&#xff1a; int register_kprobe(struct kprobe *p) {int ret;struct kprobe *old_p;st…