IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> c语言进阶篇:宏 -> 正文阅读

[C++知识库]c语言进阶篇:宏

define本质上是将文本内容在编译时进行替换,又因为他可以替换参数所以就出现了宏。

#define替换文本

语法:#define name stuff

举一个例子

#define MAX 1000
#define reg register          //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)     //用更形象的符号来替换一种实现
#define CASE break;case        //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                          date:%s\ttime:%s\n" ,\
							__FILE__,__LINE__ ,\
							__DATE__,__TIME__ )   

我们C语言的程序员内部有默契,我们把#define定义的一般做全大写函数命名一般不是全大写一般是首字母大写或其他部分大写,当然我们宏定义假做成函数也会没有全大写。

#define定义宏

#define允许把参数替换到文本中,这种实现通常被称为宏,或定义宏

definename(由逗号隔开的符号) stuff

举例:

#include<stdio.h>
#define SQUARE(x) x*x
int main()
{
	printf("%d", SQUARE(3));
	return 0;
}

![[202201210943514.png]]

值得注意的是:
上面的宏其实有一个很大的弊病.

我们用下面的代码来说明:

#include<stdio.h>
#define SQUARE(x) x * x
int main()
{
	printf("%d", SQUARE(3+2));
	return 0;
}

![[202201210945131.png]]

这并不是我们想要的答案我们想得到5*5可是这个宏定义给的式子却给了我们11这是因为我们在定义宏时没有考虑到运算的优先级.

首先我们的宏在编译阶段会直接和代码替换本次的宏就将printf函数内容进行了替换使SQUARE(3+2)替换成了3+2*3+2这样我们就得到了11的值。

所以使用宏的时候一定要加括号!加括号!

#include<stdio.h>
#define SQUARE(x) ((x) * (x))
int main()
{
	printf("%d", SQUARE(3+2));
	return 0;
}

就不会出现以上情况了。

** #define 替换规则 **
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:

  1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索
  3. 传递给函数宏的参数不能包含看似预处理指令的标记。
#define A 100
printf("A"); //不行
"#define A 100" //不行
字符串宏常量
//第一个宏,字符串没有带双引号,直接报错
#define PATH1 hello!

//第二个宏,字符串带上双引用,有告警,能够编译通过。不过windows中路径分割符需要\\,输出乱码,改过之后,正常  
//双斜杠是为了防止转义成别的字符
#define PATH1 "C:\\Users\\Desktop\\C语言"  

注:可以使用反斜杠进行续行。

用宏定义充当注释符号

直接上结论: ** 是先执行去注释,在进行宏替换。**
(可以去linux平台上试一试)

//当前,我们用BSC充当C++风格的注释  
#define BSC //  
int main()  
{  
  BSC printf("hello world\n");  
  return 0;  
}

//结果
hello world
//我们发现,并没有报错  
//甚至可以运行,但是目标代码并没有被注释掉

//查看预处理过程  
int main()  
{  
  printf("hello world\n");  
  return 0;  
} 

倘若,先执行宏替换,那么先得到的代码应该是。

int main()  
{  
  //将BSC替换成为‘//’  
  // printf("hello world\n");  
  return 0;  
} 
//再执行去注释,那么代码最终的样子,应该是  
int main()  
{  
  return 0; //printf被注释掉  
}

但实际并非如此。 所以是先执行去注释,在进行宏替换

//先去掉宏后面的//,因为是注释  
#define BSC // //最终宏变成了#define BSC  
int main()  
{  
  BSC printf("hello world\n"); //因为BSC是空,所以在进行替换之后,就是printf("hello world\n");  
  return 0;  
}

再来一个例子:

#define BSC //
#define BMC /*  
#define EMC */

//注意:EMC已经被是先被注释掉,BMC成为空

int main()  
{  
  BSC printf("hello world\n");  
  BMC printf("hello bit\n"); EMC  
  return 0;  
}

//预处理之后
int main()  
{  
  printf("hello world\n");  
  printf("hello bit\n"); EMC  
  return 0;
  //在宏定义那里,EMC已经被注释掉了,所以这里无法替换
}

宏定义中的空格

#define INC(a) ((a)++) //定义不能带空格  
int main()  
{  
  int i = 0;  
  INC (i); //使用可以带空格,但是严重不推荐(这种地方还是不要特立独行的好)
  printf("%d\n", i);  
}

#和##

介绍这俩之前我们需要先知道

	char* p = "hello ""bit\n";
	printf("hello"" bit\n");
	printf("%s\n", p);

他们的打印结果都是"hello bit"这是因为字符串有自动连接的特点.

  • 字符串的自动连接
  • # 把一个宏参数变成对应的字符串
  • ##将两个符号合成为一个符号

介绍一下#操作直接上例子:

#define PRINT(x) printf("The "#x" value is %d",x)
int main()
{
	int a = 10;
	PRINT(a);
	return 0;
}

![[Pasted image 20220227161626.png]]
这样define里的#x和"x"一样.

#define PRINT(x,y) printf("The "#x" value is" #y,x)

##的作用把两边的符号合成一个符号.

#define ADD_TO_SUM(num, value) sum##num += value
int main()
{ 
	int sum1 = 20;
	int sum2 = 10;
	ADD_TO_SUM(1, 20);//sum1+=20
	printf("%d", sum1);
	return 0; 
}

![[Pasted image 20220227161705.png]]
注: 这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。

带有副作用的宏参数

x+1;//不带副作用
x++;//带有副作用

不要对宏使用带有副作用的参数.

如下例

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
int main()
{
	int x = 5;
	int y = 8;
	int z = MAX(x++, y++);
	printf("x=%d y=%d z=%d\n", x, y, z);
	return 0;
}

![[Pasted image 20220227162001.png]]
不然总会带来我们不想要的结果因为y进行了两次++而x进行了一次++ z得到的是y++的值也就是9当然我们知道宏MAX是什么可以轻松反推但是未来大型项目中这样搞不知会出现什么bug.

undef

移除宏定义

#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。

![[Pasted image 20220227163330.png]]

2个问题:

  1. 宏只能在main上面定义吗?
  2. 在一个源文件内,宏的有效范围是什么?
#define M 10  
int main()  
{ 
	# define N 100  
printf("%d, %d\n", M, N);  
return 0;  
}

![[Pasted image 20220227162302.png]]
这个也是可以的。
结论:宏定义在哪都可以,一般放在最上面。

这样呢?
![[Pasted image 20220227162715.png]]
这样会报错。
![[Pasted image 20220227163129.png]]
结论:宏的有效范围,是从定义处往下有效,之前无效。

一个问题

![[Pasted image 20220227063358.png]]
![[Pasted image 20220227153731.png]]
现在貌似没问题,但是
![[Pasted image 20220227153907.png]]
如果是这样呢?
直接报错。
![[Pasted image 20220227154019.png]]
else悬空的问题,看图就能懂√

结论:
宏定义多条语句在特定场景下可能会有一些问题。

方案1:
if和else带上花括号。
但是不是所有的用户都有带上花括号的习惯,为了代码的可维护性,我们还要进行修改。

方案2:
主动给宏加上花括号。
但是报错了,原因:
![[Pasted image 20220227155349.png]]
多了一个;
我们不能强迫程序员不加分号,所以方案二作废。

方案3:
dowhile结构。

#define INIT_VALUE(a,b) \  
        do{a = 0;b = 0; }while(0)
   
int main()  
{  
  int flag = 0;  
  scanf("%d", &flag);  
  int a = 100;   
  int b = 200;  
  printf("before: %d, %d\n", a, b);  
  if(flag){  
    INIT_VALUE(a,b);  
  } 
  else{  
    printf("error!\n");  
  }
  printf("after: %d, %d\n", a, b);  
  return 0;  
}

结论:当我们需要宏进行多语句替换的时候, 推荐使用do-while-zero结构。当然,如若可以,宏方面的东西,推荐少用。

宏和函数的对比

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

#define MAX(a, b) ((a)>(b)?(a):(b))

那为什么不用函数来完成这个任务?
原因有二:

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

当然和宏相比函数也有劣势的地方:

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

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

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

一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。

那我们平时的一个习惯是:
把宏名全部大写。
函数名不要全部大写。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-03 15:50:34  更:2022-03-03 15:51:22 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:51:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码