赞
踩
因为一直好奇预处理器的工作机制,所以就查了查书,做一下这方面的笔记。
虽然顶着预处理器的名头,但预处理器却并不是第一个就拿到源代码的。因为在对程序进行预处理之前,编译器会对源代码进行几次翻译工作,诸如:将字符映射到源字符集上、将注释换成空格等等的处理,这才是真正的第一步预处理。而为什么这些东西要交给编译器来处理呢,这是因为“预处理器不能理解C,它一般是接受一些文本并将其转换为其他文本”,这句话就是说:C预处理器它并不明白C语言的代码规范,它会的只是将一些文本转换为另一些文本。仔细想想C预处理器的功能是不是和记事本中的“替换”功能很像哈哈。
当编译器按照C语言的代码规范将源代码进行处理之后,接下来就轮到了我们的预处理器登场了。预处理器会寻找可能是预处理指令的文本,而寻找这些指令的重要依据就是“#”符号,讲到这就要说一说预处理的主要任务了。
1、转换处理(替换)。
做这方面的预处理指令有很多,如我们常用的#include和#define预编译指令,他们基本的工作方式还是有些相同的,#include指令是将头文件中的源代码注入到该指令所处的位置;而#define则是定义了将一些字符A映射到另一些字符B上(命令约定),从而使得只要改动A就可以让预处理器去修改B的值,这种牵一发而动身的做法可以使得我们节约很多时间。但是要注意的是,他们都仅仅是将一些文本替换成了另一些文本,因此这种转换处理用的好是事半功倍,用的不好也会使得自己崩溃啊。2、条件编译(有选择性的包含源代码)。
听起来这个名字好像是编译器要干的事,但是这却实实在在是预处理器要完成的工作。虽然之前说预处理器不理解C的代码规范,但是这并不会影响到他自己拥有自己的规范啊,诸多的预编译指令就是预编译器的语言规范。而为条件编译准备的预处理指令也有很多,诸如#ifndef、#elif和#endif等等。所有的也这些都是让我们有选择性的将我们编写的代码来交给编译器,进而让编译器来编译我们的代码。
当然了,预处理器的作用还有很多,上述的作用也只是在我看来比较重要的,如果有错误还望批评指正。
#define指令最为典型的问题就是:符号的优先级问题,如下所示:
#include<stdio.h>
#define f(x) x*x
int main()
{
int a=10,b=5,c;
c=f(a+b);
printf("c的值为:%d\n",c);
c=(a+b)*(a+b);
printf("c的值为:%d\n",c);
return 0;
}
这又一次说明了,C预处理器它并不懂的C语言的代码规范,它有的只是简单的文本“替换”功能。
解决方法:
#define f(x) (x)*(x)
在宏定义中使用“()”来保证优先级的正确性。可是即使这样还是会出现优先级的问题,如下所示:
#include<stdio.h>
#define f(x) (x)*(x)
int main()
{
int a=10,b=5,c;
double d=0;
d=(double)a/f(a+b);
printf("d的值为:%f\n",d);
d=(double)a/((a+b)*(a+b));
printf("d的值为:%f\n",d);
return 0;
}
当然了,这个问题还是可以通过加“()”解决,如下:
#define f(x) ((x)*(x))
但是这的确给我们增大了使用宏定义的难度,我们要频繁的使用“()”来保证优先级的正确性,但是这并不代表其就没有优点:
1、可以很容易的将所有常量的定义集中在一起,这方便我们对这些变量的修改。
2、在C语言中,大多数在调用函数时都会带来重大的系统开销,而利用#define指令的文本“替换”功能,就可以使程序块看上去像一个函数,但是却没有函数调用的开销。
而使用宏而造成的问题的确会给我们带来困扰,所以才有了之后的内联函数的出现。
我们通过一些小的测试来更好的理解文件包含指令,如下所示:
实验1:
main.cpp
#include<stdio.h>
#include"MyHead.h"
int main()
{
number=50;
printf("数值为:%d\n",number);
return 0;
}
MyHead.h
int number;
运行结果:
修改一下main.cpp文件:
#include<stdio.h>
#include"MyHead.h"
int number;
int main()
{
number=50;
printf("数值为:%d\n",number);
return 0;
}
运行结果:
从上面的小实验中,我们很容易看出MyHead.h文件中的代码的确被注入到#include指令所在的位置了,因此当我又定义一个变量number时才会出现重复定义这个错误。
实验2:
main.cpp
#include<stdio.h>
#include"MyHead.h"
#include"TestOne.h"
int main()
{
number=50;
printf("数值为:%d\n",number);
return 0;
}
MyHead.h
#include"TestOne.h"
TestOne.h
int number=100;
运行结果:
这个错误再次印证了预处理器只是将文本进行了简单的“替换”而已,main.cpp文件重复的包含了文件TestOne.h中的内容,才导致了number变量的重定义。而解决方法就是条件编译,这也是我以前会经常犯的错误。
TestOne.h
#ifndef _TestOne_ //如果没有定义_TestOne_变量,执行下面代码,如果定义过了,就不执行下面代码
#define _TestOne_
int number=100;
#endif
这样就保证了TestOne.h中的代码只会被编译器编译一次,从而保证了代码不会因为重复包含文件而出现错误。这也解释了下面了这种情况为啥不会报错:
main,cpp
#include<stdio.h>
#include<stdio.h>
int main()
{
return 0;
}
最后总结一下,我知道我可能对预处理器认识的还不够清楚,而且很多命令也没有在这里谈,诸如#pragma(编译指示指令)、#undef等等,我这也只是看过书之后的有感而发,就是怕自己忘了所以才写了这篇笔记,如有错误还请指正啊*~*。
参考书籍:《C primer plus》、《C和指针》、《C专家编程》和《C陷阱和缺陷》
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。