预处理详解

前文提到,代码要变成可执行文件会经过许多步骤,而预处理就是一个比较重要的步骤,在预处理阶段,编译器会完成许多工作。那么接下来,就让我们仔细的对预处理阶段完成的工作进行详解吧!

1.预定义符号

c语言设置了许多的预定义符号,是可以直接使用的,预定义符号也是在预处理阶段进行直接处理的,下列就是一些比较常见的预定义符号。FILE表示该文件保存到哪里,LINE表示当前行数,DATE表示编译时的日期,TIME表示编译时的时间

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	return 0;
}

而在运行以后,也确实打印出了对应的内容。

2.#define

#define MAX 200

define在C语言中的使用非常广泛,它的基本功能就是将代码中所有的第一个参数直接转换为第二个参数。如上图代码,它会将全图里的MAX转换为200.它在使用的时候是可以代替关键字的。比如,我们觉得每次敲代码就需要敲register 这个实在太长了,那么我们就可以利用#define将他变成reg,那样,在后续敲代码的时候,我们都不需要敲register了,只需要写reg就行。

#define reg register

有时候我们需要将一串的长东西定义为1个字符,如果只是把代码一直叠加在一行,那显然既不美观也不适用,那么,我们是否有什么方法可以把定义的代码跨行写呢?

#define NO_MEAM_PRINT printf("nihao, wo shi \
                              %c,I AM a man,\
                              come from %c."\
                              name,where)

在定义中为了换行,我们可以使用\(续行符)来进行续行,这样子我们就可以把把比较长的内容分成几行来写了。

那么再来一个小问题,在define定义的时候,要不要在最后加上;呢?最好是不要,因为define的功能是直接替换,如果加上了;符号,它会把这个符号也替换过去,这不符合我们的本意。

#define 定义宏

#define的机制包含一个规定,它是允许把参数替换到文本中的,这种实现方法被称为宏,或者叫做定义宏,下面是我举的一个小例子

#define cul(a,b) a-b

如上图所示,我写了一个宏,这个宏接受两个参数分别是a和b,当我们在代码中写比如cul(4,5)时,它就会执行宏指令,得到-1的值.

但是呢,在宏指令中有一些问题,那就是他对于a,b这些参数,只是直接替换,若a或者b是表达式,它也不会进行运算,而是将这个表达式原封不动的的放入函数之中。如下图所示,按照我们的常规理解,最后的z应该是30,但是并非如此,这个宏定义会先把a+1和b+1传过去,一直传到x*y处,那么就是a+1*b+1,最后答案应该是10.为了避免这种明显不符合我们预期的事情出现,我们可以在x和y处加个小括号。这样就可以解决这个问题了。

#define SQUARE(x,y) x*y

int a = 4;
int b = 5;
int z = SQYARE(a+1,b+1);

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

 还有第二个问题,当我们在使用这个宏的时候,也是直接把所有代替的代码替换过来的,那么也会出现问题,如下图所示.按照我们本来的认知,它应该是等于90对吧?但是呢,它最后的答案是45,因为在printf里面的宏替换过去以后的结果是10*4+5,得出的答案就是45.为解决这类问题,最好的方法就是多加括号,把宏定义表达式给括起来就好了。

#define SQUARE(x,y) x+y

int a = 4;
int b = 5;
printf("%d",10*SQUARE(a,b));

而默写参数带有副作用,在进行宏定义的时候会带来一些我们并不想要的结果,譬如以下代码

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

int a = 1;
int b = 2;
int z = MAX(a++,b++);

 这串代码最后得出的结果是a = 3;b = 3;z = 2;为什么呢?刚开始的时候MAX宏定义将a++和b++传过去,结果max里面就是a++和b++,在第一次判定之中,先判断出x小于y成立并且两个++表达式执行一次,这是a = 2,b = 3,接着执行x的内容,先使用再返回,所以x收到的就是a = 2的值,然后a再++,得到的就是a = 3,。由此可见,利用宏定义在某种程度上确定会带来一定的误解。

所以,在使用宏定义的时候,我们需要按照以下步骤:

1.在调用宏时,首先对参数进行检查,查看是否包含任何由#define定义的符号,如果是,他们在最开始就需要被替换。

2.替换文本随后就被插入到程序中原本文件的地方,然后宏的的参数名被值所替代。

3.最后再对结果文件进行扫描,查看其是否包含任何由#define定义的符号,若有,就重复上述的处理过程。

但是有几点需要注意一下,第一点是宏参数和#define定义中可以出现其他被#define定义的符号,但是对于宏本身,他不能想函数一样调用自己,也就是递归

当预处理器搜索#define定义的符号时,字符串常量的内容不会被搜索。

宏与函数的比较

在了解了上述宏的功能以后,我们可以明显的发现,宏与函数的作用非常的相似,那么宏与函数各自的优缺点都有哪些呢?我们什么时候用宏,什么时候用参数呢?下面将一一解答。

宏的优点:首先,宏常用于简单的运算,如在两个数之间找到较大的一个,简短的运算用宏在程序的规模与速度方面更快一点。因为函数的调用除了处理运算阶段以外,还包括调用参数以及返回值这两步,这两步也会额外的占据之间,在简短的代码里面,这两步占的时间就蛮长的了。但如果是比较长的代码里面,这两步所占的时间就基本可以省去了。

第二点:函数的参数必须是特定的类型,由最开始的定义所决定,所以函数只能用于类型合适的表达式。反之,宏则可以用于整形长整形浮点型等等类型,宏的参数与类型无关。

宏还可以做到函数做不到的事情如下图:

#define MALLOC(num,type)\
        (type*)malloc(num sizeof(type))
{
    内容----
}


MALLOC(10,int);
预处理替换之后:
(int *) malloc(10 ,sizeof(int ));

借由这个功能,我们可以利用宏实现一些另类的模块化编程。 

但是宏也有一些缺点,如下:

1.每次使用宏的时候,会把宏代表的表达式插入程序中,这很有可能会大幅度增加程序的长度。

2.宏无法进行调试,不利于找bug。

3.宏因为类型无关,不够严谨,可能会出现一些我们不是很希望发生的事情。

4.宏可能会带来运算符优先级的问题,需要时刻警惕。

#与##的使用

#运算符的功能是将一个参数转换为字符串字面量,它仅出现在带参数的宏的替换志宏。在字符串中有个小知识点,当字符串出现多个“”符号时,它会自动将所有这个符号给拼装在一起,合成一更长的字符串。#所执行的操作可以理解为“字符串化”。具体解释如下图:

#define PRINT(n) pirntf("这个数值"#x"是%d",x);
int a = 5;
PRINT(a);
预处理之后:pirntf("这个数值a是%d",a);

 这个也可以实现比较方便的处理使用。

##运算符的功能是粘合,##运算符可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创造标识符。##被称为记号粘合。这样的粘合必须创造出一个合法的标识符,否则其结果是未定义的。这个运算的应用场景不广,在这里我们就浅举一个例子:

#define MAX(type) \
type type##_max(type) \
{                     \
    return(x>y?x:y)   \
}                     \

 当我们利用以上宏进行定义的时候,我们就可以泛泛的使用许多类型的比较了。

条件编译

在编译程序的时候,我们要将一条或一组语句放弃或编辑或搁置是很方便的,因为我们有条件编译指令。条件编译指令能够帮我们快速的对指令进行设置。下列是常见的条件编译:

#define X 1
#undef  取消宏定义
#if  X 常量表达式(该常量表达式由预处理器求值)

-----内容------

#endif   结束语句

多条分支的条件编译
#if    常量表达式

#elif   常量表达式


#else   

#endif   

3判断是否被定义
#if defined(symbol)
#ifdef  symbol (用于判断宏是否被定义)


4嵌套指令
#if defined(xxxx)
      #ifdef  case1
      #endif
      #ifdef case2
               xxxxxxxxxx内容xxxxxx
      #endif 
#elif defined(xxxxxx)
       #ifdef case2
                 xxxxxxxxx内容xxxxxxxxx
       #endif
#endif

头文件的包括

#include "stdio.h"//先在源文件下面巡查哦,
                    没找到就去标准位置(存放库函数头文件的地方)里面查找
#include <stido.h>//直接去标准路径下寻找,找不到就报错

如上图所示,""和<>包含头文件的用处有所不同,理论上" "可以包含所有头文件,但是在使用库函数的头文件时,还是请使用<>,这样子效率会高一点。

我们在多次引用同一个头文件的时候,会对一个头文件进行多次编译,给电脑和编译器带来比较大的负担。那么,有什么方法可以解决这个嵌套文件的问题吗?答案就是条件编译

#ifndef __TEST_H__\\如果没有定义过头文件,实际上这个名字无所谓
#define __TEST_H__\\那就定义一下

内容

#endif

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值