现在的位置: 首页 > 综合 > 正文

(十) 程序文件、预处理指令

2013年10月11日 ⁄ 综合 ⁄ 共 6679字 ⁄ 字号 评论关闭

1、每一个CPP及它所包含的所有头文件称为一个转换单元。
      编译器处理每个转换单元的过程与生成目标对象的过程是无关的。

2、编译只负责把每个转换单位生成目标文件。目标文件由链接程序链接成exe,这个链接包括处理一些链接属性,比如extern.
     extern用来说明本文件中此变量,已经在另一个文件定义了。因此在本文件同一块作用域中,不再有相同的定义变量,如:
      main(){ int K=3;
                       extern int  K; //错误,两个K作用域在同一块中。另外注意:引用外部变量不能再次定义赋值,否则最边链接程序连接时会造成重复定义。
                    }
        当然,如果每都是引用extern,却没人一个文件真正地定义它,那么编译器也会出错。
     有时为了定义一个全局变量在本文件中不能更改,因此申明为const,但const只具有内部链接,也就是外部的文件再无法用extern来引用它。怎么办呢?
      这时只需在定义前面加上extern并初始化它,表示它既是常量(内部链接),同时也可被其它文件链接。其它文件链接时应extern const同时写上(不能再定义赋值)
            file1.cpp        extern const PI=3.14159;                         file2.cpp:    extern const PI;  //文件2中不能再定义,但需写上extern const

3、了解:链接属性(linkage)
      转换单元的名称在编译或链接过程中的处理方式由链接属性决定.
      一、编译:严格来说,是指源代码到目的对象的过程。
              链接:目标代码到可执行文件exe的过程。
                    平时我们说编译过程指的是: 编译和链接
       二、链接属性只由链接来处理。编译过程保留那些链接属性,最后由链接程序时行处理最终进行链接。
              当某名称用于作用域外的代码块访问程序的变量或者函数时,就有了链接属性。否则就无。链接属性有三种:
                               内部链接属性:在同一个转换单元任何地方可访问:全局变量、const变量
                               外部链接属性:在另一个转换单元中可以访问的:extern,即整个程序共享和访问
                               无链接属性:只能在本作用域中访问,如块内定义 的局部变量。

4、file1.cpp:

const int j=6;

      file2.cpp:

#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
	extern const int j;
	cout<<j<<endl;
	return 0;
}

      一、多文件应用“创建工程”来进行,这样每新增加一个文件都放到“源文件”中。
      二、多个文件,有且只有一个主函数Main()
      三、因file1中用了const,说明是内部链接,因此不能在外部file2中进行引用输出。不出错的改法:
                             file1中前加extern,这样就有外部链接属性,file2中就不会出错。
                             在file2中把cout<<j<<endl;注释掉也不会出错。为什么呢?原来file2中j就是不引用file1的j中了,而是另起炉灶,定义一个可被外部引用的j。
       四、全部变量,如果没有组出初值,其值初始化时自动为0.

5、命名空间。相当于一个结构体的形式。
     一、定义:namespac  My_Name{  }
                        右花括号没有分号。把定义的变量、函数等放在其中,它们受My_Name统管。注意不能把main()放在其中。。
                        同一文件中可有多个同的命名(My_Name),但其内的变量和函数只能唯一定义一次;
                                                              namespace  OK{int  i=3;int j=4}
                                                              namespace  OK{int m,int n}    //这里不能再定义给i,j定义赋值,因为处于同一文件中。
                         不同文件中可有多个相同的命名(My_Name),同样,其定义赋值只能一次,另一处只能是引用说明是外来的。
                                                file1.cpp:    namespace OK{ int i=3; int j=4}
                                                file2.cpp:    namespace OK{extern int i;extern int j}    //引用是外部变量,不能再赋值。
               定义还可以嵌套:  namespace A{
                                                                                    .................
                                                                                   namespace B{
                                                                                                                        }
                                                                                   .............
                                                                         }
       二、使用命名空间:
                 using namespace My_Name
                   如果是嵌套:using namespace A::B;   引用时要逐个进行限定如 A::B::max();
               内部过程:在使用未限定的变量名(函数名)时,编译器在使用前,首先在本作用域内查找其定义,若无,再到外层块中查找,一直继续查找到全局作用域为止。
                                    最后实在没有定义(或不是外引用extern),则断定其没有定义,没有定义的东西,你说会发生什么呢?!
              -----------------------------------------------------------------------------------
             一般做法:做一个h文件进行声明,然后单独在另一个CPP中进行详细定义(应include那个头文件),然后在主文件中引用这个头文件。就可以使用这个了,另外在主函数使用时,注意要用作用域符进行限制(在文件首进行限定,或者在块中进行限定)
             注意:如果是模板呢?
                         应把模板定义(含代码)全放进namespace中,于头文件中。具体定义的cpp就不要写了。主函数直接引用头文件,即可自动生成对应函数并使用。
                         当模板含有特殊情况时,应把特例声明放入头文件中,并在另一个CPP(把头文件含进来)进行详细代码定义。再按正常情况进行使用。

6、命名空间的别名
      在多人编写同一项目时,为了使别人更易懂,命名空间定义名往往很长,引用起来很麻烦,于是“定义”它一个别人来引用更快快捷方便:
             namespace    myName=This_is_another_namespaceName_with_XiaoZhao;//于是后面只要用myName就相当于别人的命名空间一样。    
                                                

7、没有名字的命名空间:
      namespace
      {                   }
      无名的命名空间是可行的,它由系统指定一个内部ID来进行识别。注意:同一个文件中,如果有多个无名的命名空间,则被当作第一个无名的命名空间的扩展,即它们是同一个无名的空间内,故同一个转换单元中所有无名空间是同一空间。不同的文件的无名命名空间是各不相同的。因此,它们只在本地起作用,不能被外部引用。在C中常 用static来说明这个变量或函数是本地转换单元的。

8、预处理:属于编译器的一部分,在编译前处理。其指令前都有#,但有#的并不一定是预处理指令(如#impor)。
       #include     头文件包含
       #if                if
       #else          else
       #elif            else if
       #endif         endif(if的结束)
       #if defined(或#ifdef)      如果定义了
       #if   !defined(或ifndef)    如果没有定义。。。
       #define        定义一符号    
C中常用,C++时常用const进行定义

       #undef        删除前面定义的符号
       #line           重新定义当前等号和文件名
       #error         输出编译错误消息,停止编译
       #progma   提供机器专用的特性,同时保证与C++的完全兼容
 

9、#define 标识符   字符序列

      用给定的“字符序列”来替代“标识符。#define有三个缺点:

                                               不能进行类型检查;

                                               不考虑作用域;

                                               不能限制在同一个命名空间内。

      取消已经定义的标识符: #define  value      //后不接字符序列,就自动取消已经定义的

                                                   #undef   value    

      宏置换:复杂的#define定义

             #define    标识符(标识符列表)     替换字符串

         如:#define    print(var)     cout<<(var)<<endl  //注意语句后无分号,也可有分号,但注意它会将分号也替代进去。

                #define     print(num,var)   cout<<setw(num)<<(var)<<endl   //带2参数,亦可多个参数。

     宏置换较多,但一般都写成内联的函数或函数模板:

               template<class T>inline  void  print(T    num,T   x)

               {  cout<<setw(num)<<x<<endl;   }

      宏置换只是“死板”地替换,注意下面的错误:

               #define  shengfa(m,n)   m*n

               y=shengfa(y+1,x)   //其替换后是:y=y+1*x        故应改进为:#define shengfa(m,n)    ( (m)*(n)) 注意为啥最外还要有括号

10、预处理指令的继行: 一般应写在一行中,若一行写不完,应在最后加\ 后 ,在下一行中进行再次写,这样表示是属于上一行的继写。

11、宏字符串的实现:

       #define   print(var)   cout<<#var<<"="(var)<<endl;

       lookie=342;

       print(lookie);     //替换成:   cout<<"lookie"<<"="<<(var)<<endl;   即结果:lookie=342

       因为宏指令不能实现参数的字符串化,于是用#带参数,实现把这个参数放在引号内。

      a##b    把两个字符刻意组合在一起

      #a         把参数a放入双引号内

12、#if   defined  标识符

          ................

        #endif

      --------------------------------------------

       #ifdef    标识符

             ....................

       #endif

      ------------------------------------------------

       #if  !defined  标识符

              ....................

       #endif

      ----------------------------------------------

      #ifndef   标识符 

              ..............

      #endif

     ------------------------------------------------

      #if类似程序中一样,可以判断真假,但必须 是:#if   常量表达式

      这个常量表达式必须是整数常量表达式,否则会提示:[Error]  floating constant in preprocessor expression 类似的错误

     同样,还有#else(同else)  #elif(同else if)

13、标准预处理器宏

#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
	cout<<__LINE__<<endl;
	cout<<__DATE__<<" "<<__TIME__<<endl;
	cout<<__FILE__<<endl;
	return 0;
}

       __LINE__      前后都是两个下划线,中间大写(下同)。返回当前代码的行号,十进制整数。上例返回5(第5行)

      __DATE__     最后一次编译日期(只要未再编译,其值不变),字符串字面量。格式:mmm dd yyyy(仅月用三个英文表示)例:Sep 23 2012

     __TIME__       最后一次编译时间(同上),字符串字面量。格式:hh:mm:ss  例:09:39:38

      __FILE__       源文件名称(含路径及后缀),如:D:\cfree\show.cpp

      __STDC__     取决编译器,若编译器选项选择标准C,通常就定义它

      __cplusplus    小写,仅前面有两下划,编译C++时,定义其值为199711L

      -------------------------------------------------------------------------------------------------------------------------------------

       通常#line配合上面__LINE__和__FILE__进行。

       #line   2000     "look.cpp"      //定义从下一行以2000的行号进行计算,定义__FILE__(文件名)为look.cpp

       #line  __LINE__   "look.cpp   //只改变文件名,行号默认

       #line   324                                //只改变行号,不改变文件名

       #line   "look.cpp"                     //出错

14、#error 显示一条信息    通常用于错误调试。注意:此信息显示编译器窗体中,不会显示在输出窗体中。

        #pragma 预先定义的宏,若没出现定义过将忽略。(详情百度)

15、断言机制:assert(表达式)    表达式为假时,程序自动中断(系统调用abort()),然后弹出信息(cerr):文件名,行号,出错条件等

        在头文件   #include<cassert>

        在头文件及assert前如果有:#define  NDEBUG   断言机制将关闭(忽略)

--------------------------------------------------------------------------------------------------------

可以用<cassert>中声明的assert()库函数来检查本地C++程序中始终为true的逻辑条件

实际上assert()只在调试版本程序中才会起作用,发布版本不进行编译的;

头文件中,一般是使用预处理实现这个功能,也可以自定义调试的代码;如下所描述

在本地C++程序的中,

预处理符号NDEBUG是在发布版本中默认自动定义的.调试版本中没定义

预处理符号_DEGUG是在调试版本中默认自动定义的,发布版本中没定义

下面这个输出语句只在调试版本编译,不在
发布版本内编译,利用这个特点,可以自己写便于调试的输出信息

                               

抱歉!评论已关闭.