C++ 的预编译解释

本节知识点:

1.编译过程的简介:

   预编译:

a.处理所有的注释,以空格代替。

b.将所以#define删除,并展开所有的宏定义,字符串替换。

c.处理条件编译指令#if,#ifdef,#elif,#else,#endif

d.处理#include,并展开被包含的文件,把头文件中的声明,全部拷贝到文件中。

e.保留编译器需要使用的#pragma指令、

怎么样观察这些变化呢?最好的方法就是在GCC中,输入预处理指令,可以看看不同文件经过预处理后变成什么样了,预处理指令:gcc -E file.c -o file.i   注意:-C -E一起使用是预编译的时候保留注释。

   编译:

a.对预处理文件进行一系列词法分析,语法分析和语义分析

                词法分析:主要分析关键字,标示符,立即数等是否合法

                语法分析:主要分析表达式是否遵循语法规则

                语义分析:在语法分析的基础上进一步分析表达式是否合法

b.分析结束后进行代码优化生成相应的汇编代码文件               编译指令:gcc -S  file.c  -o  file.s

   汇编:

汇编器将汇编代码转变为机器可以执行的指令,每个汇编语句几乎都对应一条机器指令,其实机器指令就是机器码,就是2进制码。汇编指令:gcc  -c  file.c  -o file.o  注意:-c是编译汇编不连接。

   链接:

再把产生的.o文件,进行链接就可以生成可执行文件。连接指令:gcc  file.o  file1.o  -o  file  这句指令是链接file.o和file1.o两个编译并汇编的文件,并生成可执行文件file。

链接分两种:静态链接和动态链接,静态链接是在编译器完成的,动态链接是在运行期完成的。静态链接的指令是:gcc -static file.c -o file对于一些没有动态库的嵌入式系统,这是常用的。

一般要想通过一条指令生成可执行文件的指令是:   gcc file.c  -o  file

   资料:这里面说到了很多关于gcc的使用的问题,我提供一个gcc的学习资料,个人觉得还不错,也不长,就是一个txt文档,很全面。资源下载地址http://download.csdn.net/detail/qq418674358/6041183   Ps:嘿嘿,设了一个下载积分,因为真的是没分用了!希望大家见谅哈!

 

2.c语言中的预处理指令:#define、#undef(撤销已定义过的宏名)、#include、#if、#else、#elif、#endif、#ifdef、#ifndef、#line、#error、#pragma。还有一些ANSI标准C定义的宏:__LINE__、__FILE__、__DATA__、__TIME__、__STDC__。这样使用printf("%s\n",__TIME__);     printf(__DATE__);

一个#undef的例子:

[cpp]  view plain copy
  1. #include   
  2. #include   
  3. #include   
  4.   
  5.   
  6. #define X 2  
  7. #define Y X*2  
  8. #undef X  
  9. #define X 3  
  10.   
  11.   
  12. int main()  
  13. {  
  14.     printf("%d\n",Y);  
  15.     return 0;  
  16. }  

这个输出的是6,说明了#undef的作用

3.宏定义字符串的时候:应该是 #define HELLO "hello world"  记住是双引号。还有就是一切宏都是不能有分号的,这个一定要切忌!!!

4.宏与函数的比较:

   a.宏表达式在预编译期被处理,编译器不知道有宏表达式存在

   b.宏表达式没有任何的"调用"开销

   c.宏表达式中不能出现递归定义

5.为什么不在头文件中定义全局变量:

如果一个全局变量,想要在两个文件中,同时使用,那这两个文件中都应该#include这个头文件,这样的话就会出现重复定义的问题。其实是重名的问题,因为#include是分别在两个文件中展开的,试想一下,如果在两个文件中的开始部分,都写上int  a = 10;  是不是也会报错。可能你会说那个#ifndef不是防止重复定义吗?是的 ,那是防止在同一个文件中,同时出现两次这个头文件。现在是两个文件中,所以都要展开的。全局变量就重名了!!!所以 对于全局变量,最好是定义在.c文件中,不要定义在头文件中。

6.#pargma pack 设置字符对齐,看后面一节专门写字符对齐问题的!!!

7.#运算符(转换成字符串):

    假如你希望在字符串中包含宏参数,那我们就用#号,它把语言符号转换成字符串。

    #define SQR(x) printf("the "#x"lait %d\n",((x)*(x)));
    SQR(8)
    输出结果是:the 8 lait 64   这个#号必须使用在带参宏中

有个小例子:

[cpp]  view plain copy
  1. #include   
  2. #include   
  3. #include   
  4. /*在字符串中  加入宏参用的*/  
  5. #define SCAN(N,String) scanf("%"#N"s",String);  //N是截取的个数  String是存储的字符串   
  6. int main()  
  7. {  
  8.     char dd[256];  
  9.     SCAN(3,dd) //记得没有分号哈  自定义 任意格式输入的scanf  截取输入的前三个   
  10.     printf("%s\n",dd);  
  11.     return 1;  
  12. }  

8.##运算符(粘合剂)

    一般用于粘贴两个东西,一般是用作在给变量或函数命名的时候使用。如#define XNAME(n) x##n

    XNAME(8)为8n   这个##号可以使用在带参宏或无参宏中

下面是一个##运算符的小例子,代码如下:

[cpp]  view plain copy
  1. #include   
  2. #include   
  3. #include   
  4.   
  5. #define BL1 bb##ll##1  
  6.   
  7. #define BL(N) bbll##N  
  8. int main()  
  9. {  
  10.     int BL1=10;  
  11.   
  12.     int BL(4)=15;  
  13.     printf("%d\n",bbll1);  
  14.       
  15.     printf("%d\n",bbll4);  
  16.     return 1;  
  17. }  

注意:#号和##号都必须只能在宏定义中使用,不能使用在其他地方
9.其实预编译这块还有一些,不常用到的预编译指令,也是盲点,但是不难理解,用到的时候查查就好。比如说#line、#error、#warning等。

 




注:以下所用环境皆为VS2005, 由于本人编程能力及表达能力有限, 大家有看不懂的地方可以多看几遍,有错误地方请一定指出  

 

这里首先说明下几点基础知识, 相信大部分人对于以下几点大部分都已经知道了, 你也可以直接跳到最后部分看#ifndef#define#endif的真正作用

  1.预编译阶段把所有#include ”***.h“ (“”与<>的区别这里就不说了)用***.h的内容来替换了, 所以之后就没有.h了所有.h的内容都已经包含进了需要它们的.cpp中(注:该步个人认为是发生在预编译阶段) 

  2.生成最后的exe文件是由编译、链接两步完成的, 编译是源代码生成obj二进制目标文件的过程, 注意一个源代码文件(指.cpp, 而非.h, .h已经被包含进.cpp中了)生成一个obj文件, 在VS2005中单独编译一个obj的方法是选中该.cpp文件ctrl+f7, 文章中以下所说的编译皆按该方法执行而非F7, 由于编译是独立的, 所以在两个独立的编译单元里是可以有重名的函数的, 例如a.cpp中可以有一个void fun(); b.cpp中可同时有一个void fun(); 这点十分重要, 大家可以试一下并且理解清楚

  3.编译期间, 我们只要声明了的东西就能使用, 而无需它的定义, 声明可以重复, extern在编译时是告诉该编译单元该变量的定义在别的编译单元里, 相当于声明, 链接时, 定义在整个程序中有且仅有一份,  例如如下代码, 编译可通过, 但链接时失败

[cpp]  view plain copy
  1. extern int a;  
  2. //extern double a;//错误, a只能有一个类型  
  3. extern int a;  
  4. extern void fun1(int a, int b);  
  5. extern void fun1(int a, int b);  
  6. extern void fun1(int a, int b, int c);//函数重载  
  7. void fun2();  
  8. void fun2(int a);//函数重载  
  9. void fun2()  
  10. {  
  11. }  
  12. int main()  
  13. {  
  14.     a = 100;  
  15.     fun1(200, 300);  
  16.     fun2();  
  17.     fun2(100);  
  18.     return 0;  
  19. }  
  20. void fun2();  
 

 

 

  4.我们从语法上来分析下#ifndef#define#endif(这点相信地球人都知道了)

[cpp]  view plain copy
  1. //-----a.h-----  
  2. #ifndef A_H_  
  3. #define A_H_  
  4. void fun();  
  5. #endif  
 

预编译阶段, 当第一次执行该段代码(即#include "a.h",参见第一条)时, 由于我们并没有宏定义A_H_, 所以会执行#define A_H_以及void fun()两条语句, 第二次执行该段代码时因为#ifndef A_H_为假就直接走到#endif后面也就等于该次#include "a.h"什么也没做了

 

总结:

好了, 下面就我们以上所学的知识来总结一下, 来纠正大家一直以来对#ifndef#define#endif的误解

当我们一个简单的project中有三个文件main.cpp, a.cpp, a.h,而 main.cpp 和a.cpp分别包含了a.h, 在编译阶段, 两个编译单元是都会分别包含a.h的, 即使他们使用了#ifndef#define#endif, 这也是为什么当a.h被多个文件包含时我们不允许在a.h中定义变量及函数的原因, 因为在链接阶段会出现重定义。 但是在a.h中定义一个static变量却是允许的, 因为static变量是模块性作用域, 就这个例子来说, 若我们在a.h中写static int sss = 0;那么main.cpp与a.cpp使用的sss将为2个独立的sss.

那么是否#ifndef#define#endif就没用了呢, 大家可以想想, 当我们a.cpp中写了多个#include "a.h"时, 如果我们使用了#ifndef#define#endif那么预编译阶段就只会包含一个a.h中的内容到a.cpp中, 你也许会说, 有谁会傻到在a.cpp中写多个#include "a.h"呢, 那么请考虑稍微复杂点的情况, 当我们main.cpp中包含了a.h和b.h, 而a.h中我们又包含了b.h, 那么如果我们使用了#ifndef#define#endif则main.obj只会包含一份b.h


你可能感兴趣的:(C++)