宏定义

  今天,我给大家简单介绍一下宏定义的相关内容。

  在介绍宏定义之前,我先给大家介绍一下Linux,为什么介绍宏定义需要用到Linux呢,大家有没有听过IDE,全称Integrated Development Environment,中文名字叫集成开发环境,什么是集成开发环境,如微软的Visual Studio系列,Borland的C++ BuilderDelphi系列等,这都是集成开发环境。集成开发环境是用于提供程序开发环境的应用程序,一般包括代码编辑器编译器调试器和图形用户界面等工具。集成了代码编写功能、分析功能、编译功能、调试功能等一体化的开发服务 。所以大家在用vs那些开发环境的时候,发现写完代码点一下,或者按一下F5,你的代码就开始运行了。当然集成开发环境给大家极大方便的同时,也有一定的缺点,例如很多时候你需要研究一个程序的中间过程的时候,就不能很清楚的看到一个程序预处理、编译和汇编的过程。

   将程序编程目标文件的过程有三步,预处理、编译、汇编。

   在预处理阶段,主要进行以下几个内容:1.宏替换2.头文件展开3.去掉代码中的注释4.条件编译。前三个都很好理解编译器做了些什么,我说一下第四个条件编译,例如if.....else语句,在预处理阶段程序会对代码中的判断条件进行编译,大家也知道,如果if后边的判断语句是真那么就会执行,同样编译器也十分的聪明,如果你的if后边是真那else的语句编译器便不再去编译。

在编译阶段编译器做到的是将你所编写的c代码或者其他语言的代码变成汇编代码

在汇编阶段编译器做到的是将所翻译过来的汇编代码变成机器语言,就是变成机器语言二进制。

通过我简单的介绍之后,大家也就能明白,如果你想研究宏定义就不能用我们平时用的集成开发环境,你需要用到Linux系统,在Linux中一个程序变成目标文件的过程是可以划分开来的,所以这样我们就能研究程序的一步步变化。

之前我的博客介绍过一些简单的Linux的命令,今天再给大家说三个在研究代码形成过程中的指令。

gcc -E test.c -o test.i预处理完成之后就停下来,预处理之后产生的结果都放在test.i中

gcc -S test.c -o test.o编译完成之后就停下来,结果保存在test.o中

gcc -c test.c -o test.s 汇编完成之后就停下来,结果保存在test.s中

  知道这三个代码之后,就很容易的知道你的程序生成过程了。

下边进入今天的正题,介绍宏定义。

 首先给大家证明一下,宏定义是什么在生成目标文件的哪个阶段来替换的。

 宏定义

宏定义

这是我在Linux环境下编写的一段代码,输出结果为5。下边我就用我刚刚介绍的三个指令,来生成一个只进行预处理的文件。

 宏定义

生成一个只进行预处理的文件test.i,然后用vim打开之后的文件,看一下里边的内容

 宏定义

大家看我刚刚编写的程序,也只有几行,但是经过预处理之后,代码已经变成了八百多行,很多人看到之后会选择一点一点的往后翻这个文件,大可不必,在Linux中有快捷键可以直接翻到文章的末尾 shift+g就可以直接看到。

 大家看我的程序中printf后边的M已经由5来代替了,所以我们时常用到的宏定义是在预处理阶段进行替换的。

 宏定义

 最初的时候我一直认为宏定义只能用来一些文字替换程序中的数字,今天我才知道,其实宏定义可以定义的东西很多,大家看我这个程序。

 宏定义

依然可以输出正确的结果25。所以宏定义的功能并不是只能做一些简单的字母和数字的替换。

 宏定义

大家再看我更改之后的程序,大家 认为这个答案应该是什么,正常来说就是6的平方36,但是我看到这个程序的时候我认为他肯定不是36这么简单,我猜了一个26,大家应该也都能知道这个答案怎么来的5*5+1.但是事实上结果是什么呢?

 宏定义

11. 我想看到和这个答案之后肯定很多人有些不解,那我们就看一下只进行预处理之后的程序是什么样子的,来看看这个宏定义到底做了些什么。

 宏定义

看到这个5+1*5+1这个式子大家也都能明白为什么结果是11了,这同样也说明了,宏定义只是一个简单的文本替换,不会有其他的操作。

所以如果想要得出你想要的答案36就需要对程序有一些调整。

 

 宏定义

程序变成这样之后,就能得到大家所想要的答案了。

 宏定义

看我第二个程序,这个程序我想要的结果自然是100,但是事实上大家自然也清楚,因为运算优先级的问题,结果自然是55。

 宏定义

这样定义之后,那求出来的就是自己想要的结果了,所以大家在进行宏定义的时候一定要考虑周全。

 

大家都知道,宏定义大家一般都定义在代码的开头位置,或者定义在头文件中,头文件自然也是在主代码的头引入。那这么做的原因是什么呢?

 宏定义

大家知道这段代码自然能够输出5,但是如果程序是这样的呢?

 宏定义

还能不能正确的输出5

这时候再对程序进行编译就会发现

 

 宏定义

程序的编译出现了问题,所以自然就能证明,#define 他的作用域是从那一行代码开始之后的代码,之前它并不能作用到。

 之后还有一个语句就是#undef M 有时候在编写程序的时候会出现只想这个宏定义作用在某一段地方,所以,当你不想用这个宏定义的时候就可以用#undef 名字 来取消这个宏定义。

  宏定义有定义功能的,有宏替换的,但是他的替换顺序是什么呢?

  

 宏定义

 

 

 

所以#include<stdio.h>

#define square(x) x*x

#define M 5

 

int main()

{

 printf("%d",square(M));

return 0;

}

但是要注意一下几点

 宏定义

如果代码是这样的话,首先要对SQUARE的参数进行判断,如果参数也需要宏替换的话,那就先替换参数。所以这段代码中,现将M替换成5,然后将squre换成x*x,之后再去扫描是否还有需要替换的。

  我学习了宏定义之后发现宏定义的功能其实还是蛮强大,甚至代替了函数。我也有过一定的思考,为什么大家在编写程序的时候仍然都去使用函数,而很少甚至几乎没有人去用宏定义来编写程序呢?

 我给大家看一段程序

 宏定义

我算出来的结果是 6,9,8

而事实上呢结果是

 宏定义

思路是这样的,首先因为他是后置加加,所以你先对a>b进行判断,a不大于b,所以执行?后边的语句

 宏定义

比较结束之后x要加1,y也要加1,x变成了6,y变成了9,因为之后的语句是y++,然后将9赋值给了z之后还要继续加1.所以就得出了6,9,10.所以说这样的宏定义他是有一定的副作用的。

  接下来,我总体的说一下函数和宏定义的优劣

宏定义的优点:

1.宏要比函数要快,在预处理的过程中就已经实现了替换,宏比常规函数效率要高

2.宏是与类型无关的,可以应对各种各样的字符类型

缺点:

1.每次使用宏的时候,一份宏的定义将插入到程序当中,除非宏比较短,否则可能出现大幅度冯家代码的长度替换之后代码冗长

2.宏没有办法进行调试,在预处理阶段就已经替换了,当你想要进行调试的时候宏已经处理结束了。

3.宏与类型无关,是一把双刃剑。不够严谨

4.宏可能会出现运算符的优先级的问题,导致程序容易出现错误。

 所以对于初学者来说,还是不要用宏定义来实现复杂函数的功能。

  时间也比较晚了,我对宏定义的介绍先说到这里,肯定还有没有说全的地方,之后我会继续进补充。