#define 宏定义看这一篇文章就够了

news/2024/12/21 23:41:54/

        前言:在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
        这个宏接收一个参数 x,假如代码块中写有  SQUARE ( 5 ); 的话,当编译器读到的时候,预处理器就会替换为 5*5 , 这给我们的感觉就和函数一样,你给定一个值,他给你返回一个值 ,但是这样的操作也会带来部分的问题,我们给定以下代码,大家可以思考判断会输出什么
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 定义符号和宏时,需要涉及几个步骤
  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由 #define 定义的符号。如果是,它们首先被替换
  2.  替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由 #define 定义的符号。如果是,就重复上述处理过程

另外需注意:

  1. 宏参数和 #define 定义中可以出现其他 #define 定义的符号。但是对于宏,不能出现递归
  2. 当预处理器搜索 #define 定义的符号的时候,字符串常量的内容并不被搜索 

⑥ 带副作用的宏参数

        当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能 出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果
x+1;//不带副作用
x++;//带有副作用
MAX 宏可以证明具有副作用的参数所引起的问题
#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))
那为什么不用函数来完成这个任务?
原因如下:
  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多, 所以宏比函数在程序的规模和速度方面更胜一筹 
  2. 更为重要的是函数的参数必须声明为特定的类型, 所以函数只能在类型合适的表达式上使用,反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型,宏是类型无关的 

宏的缺点:

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

总结

#define定义宏函数
每次使用时,宏代码都会被插入到程序中,除了非常 小的宏之外,程序的长度会大幅度增长
函数代码只出现于一个地方;每 次使用这个函数时,都调用那个 地方的同一份代码
更快
存在函数的调用和返回的额外开销,所以相对慢一些
宏参数的求值是在所有周围表达式的上下文环境里, 除非加上括号,否则邻近操作符的优先级可能会产生 不可预料的后果,所以建议宏在书写的时候多些括号
函数参数只在函数调用的时候求值一次,它的结果值传递给函数,表达式的求值结果更容预 测
参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果
函数参数只在传参的时候求值一次,结果更容易控制
宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型
函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的
宏是不方便调试的
函数是可以逐语句调试的
宏是不能递归的
函数是可以递归的

 




本次分享就到此为止了,如有不不同观点,欢迎评论区讨论交流,感谢您的支持,码文不易,给个三连支持吧


http://www.ppmy.cn/news/1167660.html

相关文章

同步网盘选择指南:哪个同步网盘更好用?

同步盘是当下热门的云存储服务之一,它可以将您的文件在不同设备之间进行同步,使您可以随时随地访问和共享您的文件,因此受到了许多用户的喜爱。 一、什么是同步盘 首先到底什么是同步盘?同步盘是指一种云存储服务,它…

13 Multi-Head Self-Attention(从空间角度解释为什么做多头)

博客配套视频链接: https://space.bilibili.com/383551518?spm_id_from=333.1007.0.0 b 站直接看 配套 github 链接:https://github.com/nickchen121/Pre-training-language-model 配套博客链接:https://www.cnblogs.com/nickchen121/p/15105048.html 上节课回顾 0:40 At…

自然语言处理基础

自然语言 自然语言处理是人工智能能够通过图灵测试的重要工具。 自然语言处理基本的任务和应用 词性标注:把每句话的各个单词的词性标注出来,例如:形容词、名词、动词 named entity recognition命名实体的识别:识别哪些单词是真…

OpenMMlab导出swim-transformer模型并使用onnxruntime和tensorrt推理

导出onnx文件 通过mmpretrain 导出swin-transformer的onnx文件非常容易,注意需设置 opset_version12这里是一个坑,刚开始设置的opset_version11后续转换trtengine的时候会有问题。 import torch from mmpretrain import get_model, inference_modelmod…

Nginx平滑升级重定向rewrite

文章目录 Nginx平滑升级&重定向rewritenginx平滑升级流程环境查看旧版的配置信息下载新版nginx源码包和功能模块包编译配置新版本平滑升级验证 重定向rewrite配置重定向准发访问测试 Nginx平滑升级&重定向rewrite nginx平滑升级 流程 平滑升级: (升级版本、增加新功…

使用 OpenSSL 扩展来实现公钥和私钥加密

首先,你需要生成一对公钥和私钥。可以使用 OpenSSL 工具来生成: 1、生成私钥 openssl genpkey -algorithm RSA -out private_key.pem 2、从私钥生成公钥: openssl rsa -pubout -in private_key.pem -out public_key.pem现在你有了一个私钥…

Leetcode—2652.倍数求和【简单】

2023每日刷题&#xff08;四&#xff09; Leetcode—2652.倍数求和 实现代码 int sumOfMultiples(int n){int ans 0;int i 1;for(; i < n; i) {if((i % 3 0) || (i % 5 0) || (i % 7 0)) {ans i;}}return ans; }测试结果 之后我会持续更新&#xff0c;如果喜欢我的文…

Jenkins自动化部署SpringBoot项目的实现

本文主要介绍了Jenkins自动化部署SpringBoot项目的实现&#xff0c;文中通过示例代码介绍的非常详细&#xff0c;具有一定的参考价值&#xff0c;感兴趣的小伙伴们可以参考一下 1、Jenkins介绍 1.1、概念 Jenkins是一个开源软件项目&#xff0c;是基于Java开发的一种持续集成…