zoukankan      html  css  js  c++  java
  • 能否用痰盂盛饭——谈谈在头文件中定义外部变量

       “能否用痰盂盛饭”并非是一个技术问题,而是一个哲学问题。
      哲学问题没有标准答案,只存在不同的选择。
      有一种观点认为,痰盂可以盛饭。理由是只要不漏能把饭吃到嘴里就行。我看这个理由任何人都无法反驳。
      另有一种观点认为,痰盂是用来吐痰的,不可以用来盛饭。他们觉得用痰盂盛饭是一种不可理喻的行为。然而这种看法可能会被“痰盂派”视为一种不必要的洁癖。

      C语言中也有类似的“痰盂”问题:

      头文件除了可以包含函数原型和宏定义外,也可以包括结构体类型定义和全局变量定义
        ————谭浩强,《C程序设计》(第四版)学习辅导,清华大学出版社,2010年7月,p188

      在头文件中究竟能否定义外部变量呢?这个问题同样有两种选择。
      “痰盂派”会认为可以。比如
      在 abnormal.h 中写

    int e_v = 1 ;

      然后在abnormal.c中写

    #include "abnormal.h"
    #include <stdio.h>
    int main(void)
    {
      printf("%d\n",e_v);
      return 0;
    }

      没有人会说这段源程序有什么语法问题,它也没有违背C语言的任何规定。就像痰盂可以用于盛饭是一个道理。
      但是,“非痰盂派”会觉得这段源程序透着一种莫名其妙的诡异:既然定义变量e_v是为了在abnormal.c中使用,那么把这个变量定义在abnormal.c中显然最直接也最便利,为什么要舍近求远地把它定义到abnormal.h文件中呢?
      在“非痰盂派”的眼中,“*.h”文件不是用来写变量定义的,就如痰盂不是用来盛饭的一样。那么“*.h”文件是做什么用的呢?
      首先来看最简单的情况

    #include <stdio.h>
    #include <math.h>
    int main(void)
    {
      printf("%f\n",sqrt( 9.0 ));
      return 0;
    }
    

       在这段代码中,出现了printf和sqrt这样两个标识符,由于编译器在编译时不认得这两个标识符,所以需要告诉编译器这两个标识符的含义。这就是代码前面两条预处理命令

    #include <stdio.h>
    #include <math.h>
    

    的意义。这两个头文件中分别包含printf和sqrt这样两个标识符的类型声明。只有告诉了编译器这两个标识符的含义,编译器才能把它正确地编译成目标文件(*.obj或*.o)。(如果函数返回值类型为int可不声明,但这是一种落后的、逐渐被淘汰的风格)
      所以“*.h”文件的一个基本功能就是提供函数类型声明。
      另外要注意到的一点是,stdio.h、math.h是由库函数作者提供的,相当于给其他模块提供了一个使用“说明书”,使用库函数的模块用这个“说明书”向编译器说明自己所用到的在其他模块定义的函数名的数据类型。
      下面举例进一步说明。
      假设一个源程序由两个*.c源文件组成,第一个*.c提供求两个double类型数据和的函数,第二个使用这个函数。那么,第一个*.c文件的作者仅仅写出
      1.c

    double add(double d1,double d2)
    {
      return d1 + d2 ;
    }
    

     

    是远远不够的,因为这个1.c虽然可以编译目标文件(*.obj或*.o)以供连接时使用,但是在链接之前,2.c文件在编译时还需要告诉编译器add这个标识符的含义,否则无法正确地编译出与之相应的目标文件。为此,第一个*.c文件的作者还应该给出一个“说明书”——*.h文件
      1.h

    double add(double,double);
    

       2.c文件可以写为

    #include "1.h"
    int main(void)
    {
      printf("%f\n",add(3.0,4.0));
      return 0;
    }
    

      这样就解决了第二个*.c文件的编译问题(add得到了说明)。
      需要说明的是,在很多情况下1.c自己也往往需要包含这个1.h文件,因为这个1.c中可能有其他函数也需要调用这个add()函数,因而也需要这个函数类型声明。所以一般1.c写为
      1.c

    #include "1.h"
    double add(double d1,double d2)
    {
      return d1 + d2 ;
    }
    

       再来看*.h文件中出现数据类型声明的情况。
      假设在1.c中使用了一种新的用户定义的数据类型(不一定是“结构体类型”),例如

    typedef double DOUBLE;
    

      这时通常也应该把该数据类型的声明写在“*.h"文件中提供给其他模块,除非这个类型仅仅在1.c中使用。
      这时的1.h应该为
      1.h

    typedef double DOUBLE;
    DOUBLE add(DOUBLE,DOUBLE);
    

       这时的1.c为
      1.c

    #include "1.h"
    DOUBLE add(DOUBLE d1,DOUBLE d2)
    {
      return d1 + d2 ;
    }
    

       而2.c则为

    #include "1.h"
    int main(void)
    {
      DOUBLE d1=3.0,d2=4.0;
      printf("%f\n",add(d1,d2));
      return 0;
    }
    

       在这里共有两处用到了DOUBLE类型,一次是定义d1,d2这两个变量,另一次是调用add()函数。由于在1.h中这种类型已经得到了声明,所以这段代码可以顺利通过编译。
      除了函数类型声明、数据类型声明出现在*.h文件中,宏定义也可能出现在*.h中。
      如果1.c和2.c都需要一个共同的常数,把这常数作为一个符号常量写在1.h中,显然可以避免你写你的、我写我的,从而造成大家不协调一致的错误。因为这时大家都是在参照着同一个常数(宏)在写代码。
      但是,如果把外部变量的定义写在*.h中会出现什么情况呢?很显然,会出现在1.c和2.c中两次定义这个外部变量的情况,这是绝对不允许的。这就是“非痰盂派”认为在头文件中不可以写外部变量定义的理由。
     

  • 相关阅读:
    Android——继续深造——从安装Android Studio 2.0开始(详)
    PHP——安装wampserver丢失MSVCR110.dll
    Marza Gift for GDC 2016
    Retrieve OpenGL Context from Qt 5.5 on OSX
    Space Time Varying Color Palette
    Screen Space Depth Varying Glow based on Heat Diffusion
    Visualization of Detail Point Set by Local Algebraic Sphere Fitting
    Glass Dragon
    Jump Flood Algorithms for Centroidal Voronoi Tessellation
    京都之行
  • 原文地址:https://www.cnblogs.com/pmer/p/2481964.html
Copyright © 2011-2022 走看看