预处理详解(二)---#define 定义宏 + 宏的使用 + 宏和函数的区别

news2024/10/6 8:38:12

文章目录

  • #define 定义标识符
  • #define 定义宏
  • #define 的替换规则
  • 带副作用的宏参数
  • 宏和函数的区别
  • #undef 的作用
  • 冷门知识点:#与##

#define 定义标识符

#define定义标识符的格式如下:

#define MAX 100
#define reg register//懒人觉得register太长了

这些被#define定义的标识符都将在预处理阶段被编译器替换成对应的内容

之前看到一个超级有意思的东西,这里和大家分享一下:

#define mian main
#define ,
#define (
#define )
#define ture true
#define ;

确实,通过这样的特殊手段(只要在文件前面加上这几句话),就再也不用担心自己的代码中的标点符号写成中文的了,也再也不用担心把main写成mian,true写成ture了,因为即使写错了也被替换成正确的了。但是我们还是应该在写代码时认真仔细,避免这些低级错误的发生。

#define 定义宏

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

例如,用宏实现求一个数的平方:

#include <stdio.h>
#define SQUARE(x) x*x//求x的平方
int main()
{
	int ret = SQUARE(5);
	//相当于int ret = 5*5;
	printf("%d\n", ret);//结果为25
	return 0;
}

但是这并不完全正确,因为当你传入的宏参数为2+3时,打印的结果却并不是25,而是11。因为宏完成的是替换,它不会先把2+3的值算出来再进行替换,而是直接替换,所以传入2+3替换后相当于:

	int ret = 2+3*2+3;

因为*的优先级高于+,所以这样算出的结果当然是11了。为了避免这种情况的发生,用宏实现求一个数的平方应该这样:

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

这里将(x)*(x)整体再用括号括起来的原因也是一样的,都是为了避免在使用宏时,因操作符的优先级问题而导致不可预料的后果。

所以在使用#define定义宏时,不要吝啬括号,该加括号的地方就要加上。

#define 的替换规则

在程序中替换#define定义的宏和标识符时,需要涉及几个步骤

我们用以下代码进行举例:

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

1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。

例如,#define定义的宏中含有#define定义的符号MAX,则调用该宏时,首先将MAX替换。

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

2.替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。

例如,上例中经过该步骤后,代码等价于:

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

3.最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

上例不再包含任何由#define定义的符号。

注意:
1.宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。

也就是不能出现类似于以下的代码:

#define FAC(x) (x)*FAC(x-1)//error

2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索

例如,以下代码字符串中的MAX不会被替换为100,而字符串外的MAX会被替换。

#include <stdio.h>
#define MAX 100
int main()
{
	printf("MAX = %d\n", MAX);//结果为MAX = 100
	return 0;
}

带副作用的宏参数

在介绍带副作用的宏参数之前,我们先看看带有副作用是什么意思。

	int a = 10;
	int b = a + 1;//无副作用
	int c = ++a;//有副作用

代码中,b和c都想得到a+1的值,但不改变a的值。b得到a+1的值后,a的值并没有发生改变,所以无副作用;但是c得到a+1的值后,a的值也变化了,也就是有副作用。简单来说,代码执行后,除了达到我们想要的结果之外,还导致了其他问题的发生,我们就说该条语句带有副作用。

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

例如,我们要比较a和b的大小,并将其较大值赋值给c,之后再将a和b同时加1。

#include <stdio.h>
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
	int a = 10;
	int b = 20;
	int c = MAX(a++, b++);
	printf("%d\n", c);
	return 0;
}

这段代码看似没有问题,但是结果却是不正确的,因为该宏经过替换后,等价于以下代码:

#include <stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	int c = ((a++)>(b++)?(a++):(b++));
	printf("%d\n", c);
	return 0;
}

经过替换后,我们一分析便可得出答案,c的最后的结果是21,并且代码执行后,a和b的值并不是同时加1,a的值变为了11,而b的值却变为了22。

所以,当我们使用宏的时候,应该避免传入带有副作用的宏参数

宏和函数的区别

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

#define MAX(x,y) ((x)>(y)?(x):(y))

那为什么不用下面这个函数来实现这个功能呢?

int Max(int x, int y)
{
	return x > y ? x : y;
}

1.用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
2.更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。但是宏可以适用于整形、长整型、浮点型等可以用于来比较的类型。宏是类型无关的。

而且,宏有时候可以做到函数做不到的事情。例如,宏的参数可以出现类型,但是函数却不可以。

我们使用malloc函数开辟内存空间时,可能会觉得代码太多。

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p1 = (int*)malloc(10 * sizeof(int));
	if (p1 == NULL)
	{
		printf("p1开辟失败\n");
		return 1;
	}
	free(p1);
	p1 = NULL;
	return 0;
}

这时我们可以实现一个宏,使我们用malloc开辟空间时,只用传入开辟的类型和该类型的元素个数即可

#include <stdio.h>
#include <stdlib.h>
#define MALLOC(num,type) (type*)malloc(num*sizeof(type))
int main()
{
	int* p2 = MALLOC(10, int);
	if (p2 == NULL)
	{
		printf("p2开辟失败\n");
		return 1;
	}
	free(p2);
	p2 = NULL;
	return 0;
}

但是,宏也有劣势的地方,例如:

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

下面我们画出了框架图,能够更加清晰地区分宏和函数的区别:
在这里插入图片描述

#undef 的作用

#undef可以移除一个#define定义的标识符或宏。

例如,下列代码将#define定义的标识符MAX移除后,编译器便不能识别之后的MAX。

#include <stdio.h>
#define MAX 100
int main()
{
	printf("%d\n", MAX);//正常使用
#undef MAX
	printf("%d\n", MAX);//报错,MAX未定义
}

冷门知识点:#与##

这里所说的**#和##的使用非常之少**,可能有些博友都没有听说过,但是这个知识点在面试的时候也可能会被考到,也是比较重要的。

1.#的作用

这里所说的#并不是#define和#include中的#,这里所说的#的作用是:把一个宏参数变成对应的字符串。

那么,这个#到底有什么实际的作用呢?
在介绍#的作用的之前,我先向大家说明一下:字符串是有自动连接的特点的。

例如,以下案例:

	char arr[] = "hello ""world!";
	//等价于char arr[] = "hello world!";
	printf("helll ""world!\n");
	//等价于printf("helll world!\n");

接下来,给大家举一个**#的使用案例**。例如,有以下代码:

#include <stdio.h>
int main()
{
	int age = 10;
	printf("The value of age is %d\n", age);
	double pi = 3.14;
	printf("The value of pi is %f\n", pi);
	int* p = &age;
	printf("The value of p is %p\n", p);
	return 0;
}

我们发现,printf要打印的内容大部分是一样的,那么,为了避免代码冗余,我们可不可以将其封装成一个函数或是宏呢?

经过思考与实验,发现函数和普通的宏都不能实现该功能。不相信的博友可以去测试测试。

这时就需要用到这个#了,代码如下:

#include <stdio.h>
#define print(data,format) printf("The value of "#data" is "format"\n",data)
int main()
{
	int age = 10;
	print(age, "%d");
	double pi = 3.14;
	print(pi, "%f");
	int* p = &age;
	print(p, "%p");
	return 0;
}

这时我们只需将要打印的变量的变量名和打印格式传入即可。该代码经过预处理后等价于以下代码:

#include <stdio.h>
int main()
{
	int age = 10;
	printf("The value of ""age"" is ""%d""\n", age);
	double pi = 3.14;
	printf("The value of ""pi"" is ""%f""\n", pi);
	int* p = &age;
	printf("The value of ""p"" is ""%p""\n", p);
	return 0;
}

又因为字符串有自动连接的特点,所以可以打印出期望的结果。

2.##的作用

##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。

例如,下面定义的宏可以将传入的两个符号合成一个符号。

#include <stdio.h>
#define CAT(x,y) x##y
int main()
{
	int workhard = 100;
	printf("%d\n", CAT(work, hard));//打印100
	return 0;
}

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

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

相关文章

Virus Total 曝数据泄露大事件:涉及多国情报部门

The Hacker News 网站披露&#xff0c;可疑文件和病毒在线检测平台 VirusTotal 曝出数据泄露事故&#xff0c;一名员工无意中将部分 VirusTotal 注册客户的姓名、电子邮件地址等敏感数据信息上传到了恶意软件扫描平台&#xff0c;此举导致约 5600 名用户数据泄露。 据悉&#x…

Display

Pipeline Dataloader和后面网络训练是解耦的&#xff0c;Dataloader负责把数据读出来变成tensor&#xff0c;网络&#xff08;继承nn.Module父类&#xff09;负责把这tensor算成最后的输出。在网络传播的过程中&#xff0c;hook记录保留中间数据&#xff0c;用于display作图。…

CSS——基础知识及使用

CSS 是什么 CSS是层叠样式表 (Cascading Style Sheets)的简写.CSS 能够对网页中元素位置的排版进行像素级精确控制, 实现美化页面的效果. 能够做到页面的样式和结构分离。 基本语法规范 选择器 { 一条/N条声明 } 选择器决定针对谁修改 (找谁)声明决定修改啥. (干啥)声明的…

PP速度模式应用

应用场景要求&#xff1a; 电机在变化的负载下&#xff0c;保持设定的速度时&#xff0c;需要使用速度模式。 在速度模式下&#xff0c;电机速度由发送到电机的电压控制。但是要改变电机的速度&#xff08;加速或减速&#xff09;需要增加或减小电机转矩&#xff0c;因此在速度…

Stable Diffusion - 编辑生成 (OpenPose Editor) 相同人物姿势的图像

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/131818943 OpenPose Editor 是 Stable Diffusion 的扩展插件&#xff0c;可以自定义人物的姿势和表情&#xff0c;以及生成深度、法线和边缘图等信…

rt-thread rtc设备驱动开发

基于pico rtc设备驱动开发 I/O设备框架RTC设备功能配置——启用Soft RTC功能配置——启用NTP时间自动同步功能配置——启用硬件RTC RT-Thread 的 RTC &#xff08;实时时钟&#xff09;设备为操作系统的时间系统提供了基础服务。应用层对于 RTC 设备一般不存在直接调用的 API &…

three.js学习

前言&#xff1a; three.js基本使用没问下&#xff0c;下面进入自定义图形 效果展示 实现 使用BufferGeometry()自定义 <script setup lang"ts"> import { ref, onMounted } from vue import * as THREE from three // 导入轨道控制器 import { OrbitContro…

Java对象深拷贝、浅拷贝之枚举类型

问题&#xff1a;为什么属于引用类型的enum不会有深拷贝浅拷贝的问题&#xff1f; 解释&#xff1a; 在Java中&#xff0c;枚举类型是一种特殊的类类型。每个枚举值都是该枚举类型的一个实例&#xff0c;并且这些实例在枚举类型被初始化时就已经被创建。这些实例在程序的整个…

2023年7月18日,File类,IO流,线程

File类 1. 概述 File&#xff0c;是文件和目录路径的抽象表示 File只关注文件本身的信息&#xff0c;而不能操作文件里的内容 。如果需要读取或写入文件内容&#xff0c;必须使用IO流来完成。 在Java中&#xff0c;java.io.File 类用于表示文件或目录的抽象路径名。它提供了一…

elementUI el-radio 无法点击的问题

<el-form-item label"B端客户类型" prop"user_type"><template slot"label"><span>B端客户类型</span><el-tooltip effect"dark" placement"top" content"B端大客户账期有效,只有设置该类型…

数据结构双向链表,实现增删改查

一、双向链表的描述 在单链表中&#xff0c;查找直接后继结点的执行时间为O(1)&#xff0c;而查找直接前驱的执行时间为O(n)。为克服单链表这种单向性的缺点&#xff0c;可以用双向链表。 在双向链表的结点中有两个指针域&#xff0c;一个指向直接后继&#xff0c;另一个指向直…

AJAX: 事件循环(举例细论)

概念&#xff1a;执行任务和收集异步任务&#xff0c;在调用栈空闲时&#xff0c;反复调用任务队列里回调函数的一种执行机制 原因&#xff1a;JavaScript 是单线程的&#xff0c;为了不阻塞 JS 引擎&#xff0c;设计执行代码的模型 JS内代码如何执行&#xff1a; 执行同步代…

暴雪娱乐遭DDoS攻击,《暗黑破坏神》等多款游戏受影响

6月25日上午11点&#xff0c;有游戏玩家反应Blizzard Battle.net无法登入、连线缓慢及网站问题&#xff0c;暴雪也证实其电玩平台遭到DDoS攻击。 暴雪娱乐的 Battle.net在线服务遭到分布式拒绝服务&#xff08;DDoS&#xff09;攻击&#xff0c;导致玩家无法正常登录游戏或游戏…

Spring Cloud Alibaba【Nacos配置动态刷新、Nacos集群架构介绍 、Nacos的数据持久化、认识分布式流量防护 】(五)

目录 分布式配置中心_Nacos配置动态刷新 分布式配置中心_Dubbo服务对接分布式配置中心 分布式配置中心_Nacos集群架构介绍 分布式配置中心_Nacos的数据持久化 分布式配置中心_Nacos集群配置 分布式流量防护_认识分布式流量防护 分布式流量防护_认识Sentinel 分布式配置…

WIN无法访问linux开启的SAMBA服务器

WIN无法访问linux开启的SAMBA服务器 打开搜索框“管理Windows凭据” 点击编辑

Goby 漏洞发布|天擎终端安全管理系统 YII_CSRF_TOKEN 远程代码执行漏洞

漏洞名称&#xff1a;天擎终端安全管理系统 YII_CSRF_TOKEN 远程代码执行漏洞 English Name&#xff1a;Tianqing terminal security management system YII_CSRF_TOKEN remote code execution vulnerability CVSS core: 9.8 影响资产数&#xff1a;875 漏洞描述&#xff1…

标注工具Labelimg,正常运行显示,但是对图片点击Create RectBox画矩形框开始闪退

问题描述*&#xff1a;标注工具Labelimg&#xff0c;正常运行显示&#xff0c;但是对图片点击Create RectBox画矩形框开始闪退&#xff0c;闪退出现以下代码 File “C:\ProgramData\anaconda3\Lib\site-packages\libs\canvas.py”, line 530, in paintEvent p.drawLine(self.p…

接口测试 Fiddler 保存会话 (请求)

目录 前言&#xff1a; 为什么要保存请求&#xff1f; 保存单个请求 打开保存的请求文件 乱码的解决方法 保存所有请求 自动保存请求的猜想 自动保存已实现 前言&#xff1a; 在进行接口测试时&#xff0c;Fiddler是一个非常有用的工具&#xff0c;它可以帮助您捕获和…

【蓝图】p27开关门互动实现

p27开关门互动实现 创建一个门 添加初学者内容包 拖拽一个门到场景中 添加一个碰撞 创建盒体触发器 左侧模式->基础->盒体触发器&#xff0c;拖拽到门上&#xff0c;调整大小 开关门互动实现 做一个开门互动 要把开门逻辑写在关卡蓝图里 门设置为可移动 打开关卡蓝…

【JAVA】方法的使用:方法语法、方法调用、方法重载、递归练习

&#x1f349;内容专栏&#xff1a;【JAVA】 &#x1f349;本文脉络&#xff1a;JAVA方法的使用&#xff0c;递归练习 &#x1f349;本文作者&#xff1a;Melon_西西 &#x1f349;发布时间 &#xff1a;2023.7.19 目录 1. 什么是方法(method) 2 方法定义 2.1 方法定义语法格…