【C C++内功心法】剖析预处理过程,详解预处理指令

如果我们在刚刚define定义标识符的最后加上"; "  ,那么预处理后的结果将会是下面这样,这就会出现语法错误。

15ef1a2725104dd8873a5f55fb8213b3.png


当然define还可以定义其他标识符,可以是个关键字,也可以是一段代码。

#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__ ) 

2 #define 定义宏

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

宏的申明方式:

  • #define name( parament-list ) stuff
  • 其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。

注意:

  • 参数列表的左括号必须与name紧邻。
  • 如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。

举例:

// 用于求两数中的较大值 
#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;
}

替换后的结果为:

80277e0dc9e940e8bcef0f349a0d966c.png


注意:如果你想运行当前代码,需要将刚才的设置改回来才行,因为经过刚才的设置后,编译器将代码预处理完就会停下来。

a28786cc330c473a8f0525e4096c196c.png


刚才代码的运行结果。

4289e726a5af4796bc83f87cf23042ae.png


注意:定义宏的时候必要的括号不能少,因为宏的本质还是替换。

举例:

我们写一个求平方的宏:

#define SQUARE( x ) x * x

这个宏接收一个参数 x ,

如果在上述声明之后,你把

SQUARE( 5 );

置于程序中,预处理器就会用下面这个表达式替换上面的表达式:

5 * 5

注意: 这个宏存其实在一个问题,观察下面的代码段:

int a = 5;
printf("%d\n" ,SQUARE( a + 1) );

乍一看,你可能觉得这段代码将打印36这个值。 事实上,它将打印11,为什么?

替换文本时,参数x被替换成a + 1,所以这条语句实际上变成了:

printf ("%d\n",a + 1 * a + 1 );

这样就比较清晰了,由替换产生的表达式并没有按照预想的次序进行求值。

解决办法:在宏定义上加上两个括号,这个问题便轻松的解决了:

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

这样预处理之后就产生了预期的效果:

printf ("%d\n",(a + 1) * (a + 1) );

这里还有一个宏定义:

#define DOUBLE(x) (x) + (x)

定义中我们使用了括号,想避免之前的问题,但是这个宏可能会出现新的错误。

int a = 5;
printf("%d\n" ,10 * DOUBLE(a));

这将打印什么值呢?

看上去,好像打印100,但事实上打印的是55,我们发现替换之后:

printf ("%d\n",10 * (5) + (5));

乘法运算先于宏定义的加法,所以出现了55的结果。

解决办法:在宏定义表达式两边加上一对括号就可以了:

#define DOUBLE(x)   ( ( x ) + ( x ) )

所以我们这里就可以将上面写的求较大值的宏优化一下。

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

提示:

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


3 #define 替换规则

在程序中扩展#define定义符号和宏时,需要涉及以下几个步骤:

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

举例:

#define DOUBLE(x) ((x) + (x))
#define NUM 66

int main()
{
	int a = DOUBLE(NUM);

	return 0;
}

这里就会先将:

int a = DOUBLE(NUM);

替换成:

int a = DOUBLE(66);

然后再替换成:

int a = ((66) + (66));

注意:

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

举例:

这里的NUM将不会被替换。

#define NUM 66

int main()
{
	printf("NUM is a macro\n");

	return 0;
}


4 #和##

我们先来看一段代码:

int main()
{
	int a = 10;
	printf("the value of a is %d\n", a);

	int b = 20;
	printf("the value of b is %d\n", b);

	return 0;
}

运行结果:

1df9e9df2365428caa09c2893041f99d.png


  • 我们发现上面的两行代码十分是相似,运行的结果也只有两处不同,那么我们可以把刚刚那两行代码(printf那两行)封装成一个函数吗?这样我们就不用每打印一个变量,都要单独写一个printf函数了。
  • 答案是可以,但是十分的复杂,使所以这时就需要用我们的宏了。

但是我们知道,宏是不能替换字符串常量的内容的,那么如何把参数插入到字符串中?

使用#

作用:把一个宏参数变成对应的字符串。(比如:N是一个宏参数,我们使用#N,然后传过去a,那么a就会自动变成字符串 “a”)

举例:

#include<stdio.h>
#define PRINT(N) printf("the value of " #N " is %d\n", N)

int main()
{
	int a = 10;
	PRINT(a); // printf("the value of a is %d\n", a);

	int b = 20;
	PRINT(b); // printf("the value of b is %d\n", b);

	return 0;
}

这里的:

PRINT(a);

将会被替换成: (提示:#N被替换成了 “a”,N被替换成了 a)

printf("the value of " "a" " is %d\n", a);

运行结果:

和上面用两个printf打印出来的结果一模一样。

c7c58b71b68343af86fbf9d0a355ddb3.png


额外补充:

C语言规定,打印字符串时可以将一个字符串分成几个子串写入。

举例:

int main()
{
	printf("san lian\n");

	printf("san"   " "   "lian\n");

	return 0;
}

运行结果:

e0782a816cbe431c8fde1c0f5a52cbc5.png


如果我们想要打印不同类型的变量,可以像下面这样:

#include<stdio.h>
#define PRINT(N, format) printf("the value of " #N " is " #format "\n", N)

int main()
{
	int a = 20;
	PRINT(a, %d); // printf("the value of a is %d\n", a);

	double pai = 3.1415926;
	PRINT(pai, %lf); // printf("the value of pai is %lf\n", pai);

	return 0;
}

运行结果:

36979afd0fec448583fdd2f8650238c5.png


## 的作用:

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

举例:

#include<stdio.h>
#define CAT(name1, num) name1##num

int main()
{
	int sanlian333 = 666;

	printf("%d\n", CAT(sanlian, 333));

	return 0;
}

这里的:

printf("%d\n", CAT(sanlian, 333));

将会被替换成:

printf("%d\n", sanlian333);

运行结果:

a61731670fa14d47b4d37b5926eff920.png

注意:像上面这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。


5 带副作用的宏参数

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

例如:

int x = 1;
int a = x+1; // 不带副作用(x的值没有改变)
int b = ++x; // 带有副作用(x的值被改变)

MAX宏可以证明具有副作用的参数所引起的问题。

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

符。否则其结果就是未定义的。


5 带副作用的宏参数

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

例如:

int x = 1;
int a = x+1; // 不带副作用(x的值没有改变)
int b = ++x; // 带有副作用(x的值被改变)

MAX宏可以证明具有副作用的参数所引起的问题。

[外链图片转存中…(img-TVTJLRHU-1714429693620)]
[外链图片转存中…(img-kMk1NitX-1714429693621)]
[外链图片转存中…(img-nNn1f4xg-1714429693621)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

  • 12
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值