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

#include<stdio.h>

int main()
{
	FILE* pf = fopen("log.txt", "a"); // 打开log.txt这个文件

	if (pf == NULL)
	{
		return 1;
	}
	for (int i = 0; i < 10; ++i)
	{
		// 将所有文件信息写入log.txt文件
		fprintf(pf, "name: %s, file: %s, line: %d, date: %s, time: %s, i=%d\n", __func__, __FILE__, __LINE__, __DATE__, __TIME__, i);
	}

	return 0;
}

我们打开log.txt文件,这时就将所有的文件信息都写了进来。

0c971250ec4b408697d7530202144e14.png


__STDC__ 预定义符号

如果编译器遵循ANSI C,其值为1,否则未定义

举例:

int main()
{
	printf("%d\n", __STDC__);

	return 0;
}

在Windows的vs2019下

  • 编译器报错:未定义标识符“__STDC__”
  • 说明vs2019不遵循ANSI C

2b9680b8c64c468390aec799dfbbbb7e.png


在Linux的gcc下

  • 打印出来__STDC__的值为1
  • 说明gcc遵循ANSI C

4cb0980cfb844a0cbaceebe98ebde77b.png

ad52f163820a470181918381696e92ed.png


二、#define

1 #define 定义标识符

语法:

#define name stuff

功能:

  • 在预处理阶段,将代码中所有的name替换成stuff。

举例:

#define NUM 666
#define STR "hello"

int main()
{
	int num = NUM;
	char* str = STR;

	return 0;
}

在预处理阶段,上面的代码将会被替换成:

由于我们定义的标识符已经被替换了,所以替换后,#define 定义标识符将会被删除。

int main()
{
	int num = 666;
	char* str = "hello";

	return 0;
}

怎么验证?

  • 点击视图,然后打开解决方案资源管理器。

87dc5409100b4f8ebad21f1ac8f85432.png


  • 右击此处。

76fca208655a40a0888dac1724430d3f.png


  • 点击属性。

db20ff74453e4de0b181759940c54c98.png


  • 点击C/C++,然后进入预处理器,将预处理到文件这里的选项改成“是”

2722ee9d0d7042b99a7625e6f1470aec.png


  • 将当前文件编译一下,然后去当前路径下的Debug文件夹里面可以找到一个test.i文件(这就 是预处理完后生成的文件),然后将其打开。

f44a996818194bcf9ce966a8c0129f8b.png

fc9acf63883e40a2bc3b6892c2ca1b2e.png


  • 现在就可以看到替换前后的区别了。

0cfed2f63cf04c7aa0eecf3fd1813542.png


提问:

  • 在define定义标识符的时候,要不要在最后加上分号 "; " ?
  • 答案是不用。

如果我们在刚刚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;


![img](https://img-blog.csdnimg.cn/img_convert/42c3c10924e0506e7e60e8a7dc5b3959.png)
![img](https://img-blog.csdnimg.cn/img_convert/4277a6e3e39b1caada609cef712a89e2.png)
![img](https://img-blog.csdnimg.cn/img_convert/f1c2061288279ff70798235a186a3d96.png)

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

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

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**

M 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;


[外链图片转存中...(img-oMbsLmBa-1714166100816)]
[外链图片转存中...(img-48Ssmlvq-1714166100817)]
[外链图片转存中...(img-1cflddvr-1714166100817)]

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

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

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值