C语言——预处理(二)

本文详细介绍了C/C++中的预处理指令,特别是宏定义的用法,包括#define定义标识符、宏参数、续行符的使用、带副作用的宏参数及其潜在问题。同时,对比了宏与函数的优缺点,并提到了预处理指令#undef的作用。通过实例解析了宏替换规则,强调了在编写宏时避免副作用的重要性。
摘要由CSDN通过智能技术生成

#define

define 定义标识符

#define M 100

#define 定义M,相当于给M赋值了100,在预处理阶段会把M替换成100

一下奇怪的标识符例子:

#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__ )
#define mian main
#define ,
#define (
#define )
#define ture true
#define ;

只要加上上面5行代码,那么输入时可以减少出错

define中的续行符

#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ , \
__DATE__,__TIME__ )

后面的反斜杠表示的是一个续行符,只有在写#define 定义标识符的时候才会出现的语法,但是值得注意的是千万不要在续行符的后面加上空格、回车之类的,如果反斜杠跟空格回车组合在一起会成转义字符,那么你的程序可能会出问题,小心使用即可

define 定义宏

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

常见的错误用法

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

正确的方式

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

所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或 邻近操作符之间不可预料的相互作用。

#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定义的变量。但是对于宏,不能出现递归。
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

例如:
在这里插入图片描述

#和##

“#”

使用#,把一个宏参数名变成对应的字符串
例如
在这里插入图片描述

#不会将参数名替换成10,而是保留参数名,将这个参数名变为字符串

上面宏定义,打印的类型固定了,那么然后例如宏来变得更加灵活呢?

在这里插入图片描述

这个宏的功能是可以按照指定的格式化进行输出

“##”

  • ##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。
    例如
    在这里插入图片描述

这个宏的功能是,把两个标识符合并成一个吧,在进行文本替换(预处理宏替换)
所以最后打印的结果是100;

带副作用的宏参数

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

MAX宏可以证明具有副作用的参数所引起的问题。
在这里插入图片描述

宏替换后变成这样
//int ret = ((a++)>(b++)?(a++):(b++))
由于是后置加加,先使用后加加,先进行表达式10>11?,++后变成 a=11 b=12,在进行下一条表达式 返回b,然后a和b在++,
所以结果为 返回值 12

宏与函数对比

宏经常被用来执行简单的运行,例如两数最大值

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

那么为什么不使用函数呢?

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

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

而且,宏有时候可以做到函数做不到的事情。例如,宏的参数可以出现类型,但是函数却不可以。
在这里插入图片描述
当然和宏相比函数也有劣势的地方:

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

宏与函数的命名约定

宏名通常全大写, 函数名不要全都大写

预处理指令 #undef

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

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

谢谢收看,下一章 命令行定义 + 条件编译 + 文件包含
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

2023框框

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值