【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*n

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

int 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也是分别++一次的结果。

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

图解 Python 编程(12) | 文件和编码方式

&#x1f31e;欢迎来到Python 的世界 &#x1f308;博客主页&#xff1a;卿云阁 &#x1f48c;欢迎关注&#x1f389;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f31f;本文由卿云阁原创&#xff01; &#x1f4c6;首发时间&#xff1a;&#x1f339;2024年6月9日&am…

使用 Scapy 库编写 TCP 劫持攻击脚本

一、介绍 TCP劫持攻击&#xff08;TCP Hijacking&#xff09;&#xff0c;也称为会话劫持&#xff0c;是一种攻击方式&#xff0c;攻击者在合法用户与服务器之间的通信过程中插入或劫持数据包&#xff0c;从而控制通信会话。通过TCP劫持&#xff0c;攻击者可以获取敏感信息、执…

(2024,扩散,DMP,提示混合,动态门控,阶段特异性,微调)通过混合提示进行扩散模型修补

Diffusion Model Patching via Mixture-of-Prompts 公和众和号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0 摘要 1 简介 2 相关工作 3 扩散模型修补&#xff08;DMP&#xff09; 3.1 架构…

【召回第一篇】召回方法综述

各个网站上找的各位大神的优秀回答&#xff0c;记录再此。 首先是石塔西大佬的回答&#xff1a;工业界推荐系统中有哪些召回策略&#xff1f; 万变不离其宗&#xff1a;用统一框架理解向量化召回前言常读我的文章的同学会注意到&#xff0c;我一直强调、推崇&#xff0c;不要…

transformer中对于QKV的个人理解

目录 1、向量点乘 2、相似度计算举例 3、QKV分析 4、整体流程 (1) 首先从词向量到Q、K、V (2) 计算Q*&#xff08;K的转置&#xff09;&#xff0c;并归一化之后进行softmax (3) 使用刚得到的权重矩阵&#xff0c;与V相乘&#xff0c;计算加权求和。 5、多头注意力 上面…

python爬虫入门教程(二):requests库的高级用法

requests库除了基本的GET和POST请求外&#xff0c;requests库还提供了许多高级功能&#xff0c;本文将介绍其中一些常用的用法。包括&#xff1a; 会话保持&#xff08;Session&#xff09;SSL证书验证文件上传代理设置自定义HTTP适配器超时设置 请求参数 文章最开始&#x…

dockerhub不可用临时解决方案

近日&#xff0c;在拉取一些docker hub的镜像的时候死活拉不下来&#xff0c;要么超时&#xff0c;要么无法接站点地址&#xff0c;不管是docker hub,还是国内镜像站&#xff0c;统统都不行了。 经过各大媒体报道&#xff0c;以及自己的亲身验证&#xff0c;才知道&#xff0c…

android集成百度文心一言实现对话功能,实战项目讲解,人人都能拥有一款ai应用

大家好&#xff0c;今天给大家讲解下如何实现一个基于百度文心一言的app功能&#xff0c;app内部同时集成了讯飞的语音识别。本文适用于有android基础的小伙伴阅读&#xff0c;文章末尾放上本项目用到的全部实例代码&#xff0c;在使用前请务必看完本文章。 先来给大家看看效果…

Spring的Controller是单例还是多例,如何保证线程安全的。

目录 验证是否单例&#xff08;默认单例&#xff09; 多例测试 单例对象成员变量测试 多例对象成员变量测试 解决方案 结论&#xff1a; 补充说明 答案&#xff1a;controller默认是单例的&#xff0c;不要使用非静态的成员变量&#xff0c;否则会发生数据逻辑混乱。 正…

Windows下SVN文件损坏,启动服务报错1067

之前碰到过一次&#xff0c;忘记最后怎么解决的了&#xff0c;只记得大概原理和原因&#xff0c;以及解决办法。 1067错误码&#xff0c;很多地方都会碰到&#xff0c;mysql也会有&#xff0c;看来应该是windows系统的错误码。跟具体程序无关。所以直接百度“SVN”、“1067”…

【Python报错】已解决ValueError: Expected 2D array, got 1D array instead

成功解决“ValueError: Expected 2D array, got 1D array instead”错误的全面指南 一、引言 在Python的数据分析和机器学习领域&#xff0c;尤其是使用NumPy、Pandas、scikit-learn等库时&#xff0c;经常会遇到各种类型错误。其中&#xff0c;“ValueError: Expected 2D arr…

AI菜鸟向前飞 — LangChain系列之十七 - 剖析AgentExecutor

AgentExecutor 顾名思义&#xff0c;Agent执行器&#xff0c;本篇先简单看看LangChain是如何实现的。 先回顾 AI菜鸟向前飞 — LangChain系列之十四 - Agent系列&#xff1a;从现象看机制&#xff08;上篇&#xff09; AI菜鸟向前飞 — LangChain系列之十五 - Agent系列&#…

大模型的发展历程

1、早期模型的探索与局限性 1.1早期模型的探索与局限性 从早期的符号逻辑到现代的深度学习 1 模型&#xff0c;AI 领域经历了数十年的探索和迭代&#xff0c;为后续突破打下了坚实基础。随着大数据的发展和 AI 计算能力的爆炸式增长&#xff0c;深度学习模型的崛起显得尤为突出…

有序二叉树java实现

类实现&#xff1a; package 树;import java.util.LinkedList; import java.util.Queue;public class BinaryTree {public TreeNode root;//插入public void insert(int value){//插入成功之后要return结束方法TreeNode node new TreeNode(value);//如果root为空的话插入if(r…

人工智能_机器学习096_PCA主成分分析降维算法_PCA降维原理_介绍和使用_模式识别_EVD特征值分解_SVD奇异值分解---人工智能工作笔记0221

首先我来看PCA降维,可以看到在图像处理中经常用到PCA,经过对数据进行降维可以去除数据噪声,发现数据中的模式,也就是 发现数据的规律. 这里的模式识别就是 机器学习中的一个分支 就是在数据中找规律的意思 我们使用代码看一下 from sklearn.docomposition import PCA from skl…

kivy 百词斩项目 报错

AttributeError: FigureCanvasKivyAgg object has no attribute resize_event AttributeError: FigureCanvasKivyAgg object has no attribute resize_event 是一种常见的Python错误&#xff0c;当你试图访问一个对象&#xff08;在这个例子中是 FigureCanvasKivyAgg 对象&am…

六、主存储器管理,计算机操作系统教程,第四版,左万利,王英

文章目录 [toc]一、存储管理的功能1.1 存储分配1.2 存储共享1.3 存储保护1.4 存储扩充1.5 地址映射 二、内存资源管理2.1 内存分区2.1.1 静态分区与动态分区2.1.2 等长分区与异长分区 2.2 内存分配2.2.1 静态等长分区的分配2.2.2 *动态异长分区的分配 2.3 碎片与紧凑 三、界地址…

从C到C++,C++入门(2)

在C入门篇&#xff08;1&#xff09;中&#xff0c;博主为大家简单介绍了什么是C&#xff0c;以及C中的关键字&#xff0c;命名空间&#xff0c;输入与输出和缺省参数的相关知识。今天就让我们继续一起学习C的基础知识点吧&#xff01;&#xff01; 1.函数重载 1.1函数重载的概…

C# WPF入门学习主线篇(十九)—— 布局管理实战『混合布局案例』

C# WPF入门学习主线篇&#xff08;十九&#xff09;—— 布局管理实战『混合布局案例』 欢迎来到C# WPF入门学习系列的第十九篇。在前几篇文章中&#xff0c;我们详细介绍了各个布局容器的基本概念和使用方法。本篇博客将通过一个综合的实战案例&#xff0c;展示如何在WPF中使用…

Comfyui容器化部署与简介

目前使用 Stable Diffusion 进行创作的工具主要有两个&#xff1a;Stable Diffusion WebUI 和 ComfyUI。本文重点介绍ComfyUI的部署使用。 ComfyUI 可定制性很强&#xff0c;可以让创作者搞出各种新奇的玩意&#xff0c;通过工作流的方式&#xff0c;也可以实现更高的自动化水平…