预处理指令

 当我们按照c语言标准写出来代码,编译器将我们的代码处理为目标文件,该过程分为预编译阶段和编译阶段以及汇编,预编译阶段会处理源文件的预处理指令,本文就着重说说预处理指令部分,展现编译器编译代码中预编译等三个阶段的不同则需要用到gcc编译器,之前一直懒得搭建环境,最近才写出,现在写的这篇类似代码预处理中一个阶段的拓展。

一.预定义符号

LINEDATETIME,_ FUNCTION _(注意FUNCTION这里是双下滑线,而_FILE_在vs2019未定义,无法打印展示)
编辑

二.#define理解(本质就是替换)

 1#define定义标识符语法

#define  name  struff

 name为标识符名,如下面的MAX1,struff可以是常量,也可以是字符串,甚至是一段代码,MAX1出现的所有位置在预处理阶段都会被编译器替换为实际上对应struff的内容。

#define Max (x+y)`
#define Print printf("hehe")
#define hello "hello"  

标识符虽然可以用一个简单的符号代替许多内容,使得代码简化,但是有时候我们想往代替的文本里传递参数,标识符被定义时无该功能,宏应运而生。

2#define定义宏语法

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

#define add(x,y) (x+y)

宏由宏名,宏参,宏体组成,参照上述宏定义,宏名是add, 宏参是括号内的x和y, 宏体就是(x+y), 所以说宏就是把宏参插入到宏体的实现。下面就要说说定义宏和定义表示符的弊端

弊端1 #define的结束标志


一般来说#define定义以换行作为结束标志,而不用分号,因为分号也会被一同替换,这时候容易导致语法错误,这其实是他们的本质都是把后面的文本对应于语法中的struff的直接替换过来,分号也被编译器识别为文本的一部分,所以分号也会被替换到代码中去,如下
编辑
在if和else中未加大括号,这个时候语法规定if else中只能一条句子,替换后导致if else中间有两条,一条是max=10; 还有一条是空语句;
编辑

弊端 2:吝啬括号的后果
(1).#define Add(x,y)  x+y
printf("%d",Add(5,6)*3);`

Add(5,6)处被替换为 5+6,此时外部还有个乘法, 5+6*3这个表达式的结果不是我们想要的,归根到底就是x+y这个宏体应该用括号括起。但是这就万无一失了吗?

(2).#define  Multipy(x,y)  (x*y)  
printf("%d",Multipy(2+3,4+5))`

如果x和y本身也是个表达式,如上替换结果为:2+3*4+5,显然这结果并非是我们想要的。所以我们应该单独给x和y一个括号。如下:

#define Multipy(x,y) ((x)*(y))

标识符定义也可以在main函数内部,一般习惯在全局定义标识符,但是使用标识符要在定义后,不然编译器会报错说不认识该标识符。由于我许久没用宏,导致我都有点分不清定义宏和定义标识符了,个人综合语法理解,宏就是补充定义标识符的不足,使得我们能在替换文本时可以插入参数到宏体中,有时候我们的宏参不是字符串,却要插入到字符串中去,这就需要有能将参数字符串化的操作符了,也就是下面提到的#操作符。

3.宏替换步骤

 宏替换时会先扫描宏参中是否有其它的宏,有的话会先将其替换掉(通常情况下,有例外)
然后将宏体插入代码中宏名的位置,再把宏参直接替换到宏体中去,是直接替换,而不是值传递,但若是宏参在字符串常量内,此时不会被替换。
 之后再扫描代码中的宏体,检查宏体中是否有其它的宏定义符号,宏体中的字符串常量即便包含宏参名也不会被替换,后面有解决方法。引用
(所以宏定义中宏体不可以包括自己的宏名,不然会递归替换,陷入死循环)

4.#和##

#的作用,把一个宏参数变成对应的字符串,还有要说的一个前提就是c和c++中的字符串是会自动连接的。
所以如下代码中的"hello ""world"printf会将其连在一起并打印出来。
编辑
所以说如果宏体本身是个字符串,然后我们把宏参用#的转化为字符串,并且把#宏参和宏体放一起,此时编译器会帮我们将两个字符串相连接。
有些时候可以方便我们敲代码,比如我们写如下代码的时候

int a = 0;
printf("a的值为%d", a);
int b = 0;
printf("b的值为%d", b); 
int c = 0;
printf("c的值为%d", c);

为了打印的提示更加清晰,我们把变量名字a也打印了,这意味着这个printf函数是专为变量a服务的,那如果要打印其它变量的值,那就要再写多个printf函数, 在这些printf函数中要打印的只有变量名和变量的值是变化的,如果用函数的实现话我们想想参数要什么?首先要有变量的值传递吧,还要有它的类型,那请问变量名如何传递呢?就算都传过去了怎么用呢?怎么把参数写到printf函数的参数列表里去呢? 向一串代码中插入宏参,大家有没有想到这其实就是宏啊。

#define PRIN(Forma,vari)  printf(#vari"的值为"#Forma"\n" ,vari)

由于(“”)双引号引起的字符串的内容不会被替换,所以如果我们要把变量名从字符串中分出来,并且加上#号,如上。

#include<stdio.h>
#define PRIN(Forma,a)  
int main()
{
	int b = 3;
	PRIN(%d, b);
	return 0;
}

此时PRIN会检查参数是否有其它的宏定义,无则直接将宏体替换到PRIN(%d,b);代码处
然后参数就会直接被替换到宏体中,是直接替换,而非值传递,如下图。

c和c++在宏的规定上有些不同,c++中规定向字符串宏体插入的宏参必须字符串化,而c语言中则要求非字符串的宏参使用#字符串化,了解即可。

三#undef作用

移除一个宏定义,作用有点类似是宏的作用域边界控制,宏定义处到移除宏定义位置就是它的作用域。注意:在c/c++中宏到有效范围为当前文件有效举例解释如下,
(1)我们在使用时常常在头文件中进行的宏定义,但是程序经过编译后,头文件的内容都被包含到源文件了,在头文件中的宏定义,随着头文件一同被包含到源文件中时,此时宏定义在该源文件中有效,当头文件中的宏定义随着该头文件一起被包含到另一个头文件中,而这另一个头文件又被另一个源文件包含,则该宏定义在最终被包含的源文件中同样有效。如果多个源文件包含同一个头文件,这意味着这些源文件都各自定义了一个宏,但这些宏定义是互不影响的关系,当我们用#undef移除其中一个源文件的宏定义时,不会对其它源文件的宏产生影响。
(2)当宏定义在源文件中时,只在当前源文件中有效,即使当前源文件所对应的头文件被其它源文件包含,但是宏定义不在头文件中,所以其它源文件未包含到宏的定义,其它源文件也不能直接使用该宏定义。(也就相当于文件内的私有成员,只能被文件内的成员使用)

四 条件编译


//a.h处定义了_DEBUG_
#if _DEBUG_ 1
#include"a.h"
#endif
//#if与#endif配套出现

条件编译的应用场景:未来我们接触的工程都是几十上百万行代码的,假设有头文件a, 头文件b,源文件c, 如果源文件c包含了头文件b的内容,头文件b包含了头文件a,如果我们要在源文件c中使用头文件a中的东西,代码多了的情况,可能就不知道头文件a已被包含,而是再次包含该头文件,这导致在编译源文件的时候会出现代码冗余的情况,会影响后续软件的运行和下载。
为了防止上述情况,我们可以在头文件a定义一个宏b,当我们的源文件包含了头文件a,也就包含了这个宏的定义,所以我们可以用#if +宏名判断头文件a是否被包含,如上,如果_DEBUG_已经定义了,那下面包含头文件a代码也就不会执行了,可以减少代码的冗余。

复合条件编译也就在后面加上#elif相当于if后面加上else if形成复合条件判断。

五 头文件包含

 #include指令可以包含其他文件进行编译,我们在包含头文件的时候发现有些头文件包含#include<stdio.h>,有些则为#include"sort.h",这实质的区别是编译器会在不同的路径下找文件。
  包含双引号(“”)的头文件称为本地文件包含,编译器会先在源文件所在目录下查找,若未找到,才会像查找库函数头文件一样该头文件储存位置找,显然,#include<stdio.h>是库文件包含,编译时编译器会去头文件的安装路径查找。如果我们对一个库文件用本地文件的包含方式,编译会先去本地查找,再去头文件的存储位置查找,这使得效率降低了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小何只露尖尖角

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

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

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

打赏作者

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

抵扣说明:

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

余额充值