• extern与头文件(*.h)的区别和联系


    原文网址为:http://lpy999.blog.163.com/blog/static/117372061201182051413310/

    个人认为有一些道理:所以转过来学习了。

    用#include可以包含其他头文件中变量、函数的声明,为什么还要extern关键字?

            如果我想引用一个全局变量或函数a,我只要直接在源文件中包含#include<xxx.h> (xxx.h包含了a的声明)不就可以了么,为什么还要用extern呢??

           这个问题一直也是似是而非的困扰着我许久,经过实践和查找资料,有如下总结:

    一、头文件

            首先说下头文件,其实头文件对计算机而言没什么作用,她只是在预编译时在#include的地方展开一下,没别的意义了,其实头文件主要是给别人看的。

             我做过一个实验,将头文件的后缀改成xxx.txt,然后在引用该头文件的地方用

    #include"xxx.txt"

             编译,链接都很顺利的过去了,由此可知,头文件仅仅为阅读代码作用,没其他的作用了!

            不管是C还是C++,你把你的函数,变量或者结构体,类啥的放在你的.c或者.cpp文件里。然后编译成lib,dll,obj,.o等等,然后别人用的时候,最基本的gcc hisfile.cpp yourfile.o|obj|dll|lib 等等。
           但对于我们程序员而言,他们怎么知道你的lib,dll...里面到底有什么东西?要看你的头文件。你的头文件就是对用户的说明。函数,参数,各种各样的接口的说明。
            那既然是说明,那么头文件里面放的自然就是关于函数,变量,类的“声明”(对函数来说,也叫函数原型)了。记着,是“声明”,不是“定义”。

           那么,我假设大家知道声明和定义的区别。所以,最好不要傻嘻嘻的在头文件里定义什么东西。比如全局变量:
    /*xx头文件*/
    #ifndef _XX_头文件.H
    #define _XX_头文件.H
    int A;
    #endif

            那么,很糟糕的是,这里的int A是个全局变量的定义,所以如果这个头文件被多次引用的话,你的A会被重复定义,显然语法上错了。只不过有了这个#ifndef的条件编译,所以能保证你的头文件只被引用一次,不过也许还是不会出岔子,但若多个c文件包含这个头文件时还是会出错的,因为宏名有效范围仅限于本c源文件,所以在这多个c文件编译时是不会出错的,但在链接时就会报错,说你多处定义了同一个变量,

    Linking...
    incl2.obj : error LNK2005: "int glb" (?glb@@3HA) already defined in incl1.obj
    Debug/incl.exe : fatal error LNK1169: one or more multiply defined symbols found

    注意!!!

    二、extern

            这个关键字真的比较可恶,在定义变量的时候,这个extern居然可以被省略(定义时,默认均省略);在声明变量的时候,这个extern必须添加在变量前,所以有时会让你搞不清楚到底是声明还是定义。或者说,变量前有extern不一定就是声明,而变量前无extern就只能是定义。注:定义要为变量分配内存空间;而声明不需要为变量分配内存空间。

         下面分变量和函数两类来说:

    (1)变量

    尤其是对于变量来说。
    extern int a;//声明一个全局变量a
    int a; //定义一个全局变量a

    extern int a =0 ;//定义一个全局变量a 并给初值。
    int a =0;//定义一个全局变量a,并给初值,

            第四个 等于 第 三个,都是定义一个可以被外部使用的全局变量,并给初值。
           糊涂了吧,他们看上去可真像。但是定义只能出现在一处。也就是说,不管是int a;还是extern int a=0;还是int a=0;都只能出现一次,而那个extern int a可以出现很多次。

            当你要引用一个全局变量的时候,你就必须要声明,extern int a; 这时候extern不能省略,因为省略了,就变成int a;这是一个定义,不是声明。注:extern int a; 中类型int可省略,即extern a; 但其他类型则不能省略。

    (2)函数
           函数,对于函数也一样,也是定义和声明,定义的时候用extern,说明这个函数是可以被外部引用的,声明的时候用extern说明这是一个声明。 但由于函数的定义和声明是有区别的,定义函数要有函数体,声明函数没有函数体(还有以分号结尾),所以函数定义和声明时都可以将extern省略掉,反正其他文件也是知道这个函数是在其他地方定义的,所以不加extern也行。两者如此不同,所以省略了extern也不会有问题。
        比如:
    /*某cpp文件*/
    int fun(void)
    {
          return 0;
    }

    很好,我们定义了一个全局函数
    /*另一cpp文件*/
    int fun(void);
    我们对它做了个声明,然后后面就可以用了
    加不加extern都一样
    我们也可以把对fun的声明 放在一个头文件里,最后变成这样
    /*fun.h*/
    int fun(void);   //函数声明,所以省略了extern,完整些是extern int fun(void);
    /*对应的fun.cpp文件*/
    int fun(void)
    {
         return 0;
    }//一个完整的全局函数定义,因为有函数体,extern同样被省略了。
           然后,一个客户,一个要使用你的fun的客户,把这个头文件包含进去,ok,一个全局的声明。没有问题。
    但是,对应的,如果是这个客户要使用全局变量,那么要extern 某某变量;不然就成了定义了。

            总结:

            对变量而言,如果你想在本源文件(例如文件名A)中使用另一个源文件(例如文件名B)的变量,方法有2种:(1)在A文件中必须用extern声明在B文件中定义的变量(当然是全局变量);(2)在A文件中添加B文件对应的头文件,当然这个头文件包含B文件中的变量声明,也即在这个头文件中必须用extern声明该变量,否则,该变量又被定义一次。

           对函数而言,如果你想在本源文件(例如文件名A)中使用另一个源文件(例如文件名B)的函数,方法有2种:(1)在A文件中用extern声明在B文件中定义的函数(其实,也可省略extern,只需在A文件中出现B文件定义函数原型即可);(2)在A文件中添加B文件对应的头文件,当然这个头文件包含B文件中的函数原型,在头文件中函数可以不用加extern。

    ******************************************************************************************************************************************************

           对上述总结换一种说法:

             (a)对于一个文件中调用另一个文件的全局变量,因为全局变量一般定义在原文件.c中,我们不能用#include包含源文件而只能包含头文件,所以常用的方法是用extern  int a来声明外部变量。   另外一种方法是可以是在a.c文件中定义了全局变量int global_num ,可以在对应的a.h头文件中写extern int global_num ,这样其他源文件可以通过include a.h来声明她是外部变量就可以了。

          (b)还有变量和函数的不同举例

              int fun(); 和 extern int fun(); 都是声明(定义要有实现体)。  用extern int  fun()只是更明确指明是声明而已。

              而 int a;   是定义      

                   extern int a; 是声明。

    (3)此外,extern修饰符可用于C++程序中调用c函数的规范问题。

     比如在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同。

    C++语言在编译的时候为了解决的多态问题,会将名和参数联合起来生成一个中间的名称,而C语言则不会,因此会造成链接时找不到对应的情况,此时C就需要用extern “C”进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间名。

    三、extern和头文件的联系
            这种联系也解决了最初提出的2个问题:

    (a)用#include可以包含其他头文件中变量、函数的声明,为什么还要extern关键字?

           (b)如果我想引用一个全局变量或函数a,我只要直接在源文件中包含#include<xxx.h> (xxx.h包含了a的声明)不就可以了么,为什么还要用extern呢??

             答案:如果一个文件(假设文件名A)要大量引用另一个文件(假设文件名B)中定义的变量或函数,则使用头文件效率更高,程序结构也更规范。其他文件(例如文件名C、D等)要引用文件名B中定义的变量或函数,则只需用#include包含文件B对应的头文件(当然,这个头文件只有对变量或函数的声明,绝不能有定义)即可。

    ********************************************************************************************************************************************

           那是一个被遗忘的年代,那时,编译器只认识.c(或.cpp)文件,而不知道.h是何物的年代。
           那时的人们写了很多的.c(或.cpp)文件,渐渐地,人们发现在很多.c(或.cpp)文件中的声明变量或函数原型是相同的,但他们却不得不一个字一个字地重复地将这些内容敲入每个.c(或.cpp)文件。但更为恐怖的是,当其中一个声明有变更时,就需要检查所有的.c(或.cpp)文件,并修改其中的声明,啊~,简直是世界末日降临!
           终于,有人(或许是一些人)再不能忍受这样的折磨,他(们)将重复的部分提取出来,放在一个新文件里,然后在需要的.c(或.cpp)文件中敲入#include   XXXX这样的语句。这样即使某个声明发生了变更,也再不需要到处寻找与修改了---世界还是那么美好!
            因为这个新文件,经常被放在.c(或.cpp)文件的头部,所以就给它起名叫做“头文件”,扩展名是.h.
            从此,编译器(其实是其中预处理器)就知道世上除了.c(或.cpp)文件,还有个.h的文件,以及一个叫做#include命令。

  • 相关阅读:
    archlinux .bash_history
    Ubuntu环境下挂载新硬盘
    软碟通 UltraISO U启替代品 Win32DiskImager 无设备 无盘符 无u盘 无优盘 解决方案 之diskpart
    delphi Integer overflow
    MSBuild Tools offline
    delphi synedit免费的拼写检查器dll
    git 自定义命令行
    lua编译
    gcc ar
    Windows Subsystem for Linux (WSL)挂载移动硬盘U盘 卸载 c d 盘
  • 原文地址:https://www.cnblogs.com/feng9exe/p/7111130.html
Copyright © 2020-2023  润新知