extern 頭文件 定義&聲明

 https://www.cnblogs.com/tshua/p/5741009.htmlhtml

用#include能夠包含其餘頭文件中變量、函數的聲明,爲何還要extern關鍵字?程序員

       若是我想引用一個全局變量或函數a,我只要直接在源文件中包含#include<xxx.h> (xxx.h包含了a的聲明)不就能夠了麼,爲何還要用extern呢??函數

       這個問題一直也是似是而非的困擾着我許久,通過實踐和查找資料,有以下總結:spa

1、頭文件code

        首先說下頭文件,其實頭文件對計算機而言沒什麼做用,她只是在預編譯時在#include的地方展開一下,沒別的意義了,其實頭文件主要是給別人看的。htm

        我作過一個實驗,將頭文件的後綴改爲xxx.txt,而後在引用該頭文件的地方用對象

   #include"xxx.txt"blog

        編譯,連接都很順利的過去了,由此可知,頭文件僅僅爲閱讀代碼做用,沒其餘的做用了!接口

        無論是C仍是C++,你把你的函數,變量或者結構體,類啥的放在你的.c或者.cpp文件裏。而後編譯成lib,dll,obj,.o等等,而後別人用的 時候,最基本的gcc hisfile.cpp yourfile.o|obj|dll|lib 等等。
        但對於咱們程序員而言,他們怎麼知道你的lib,dll...裏面到底有什麼東西?要看你的頭文件。你的頭文件就是對用戶的說明。函數,參數,各類各樣的接口的說明。
        那既然是說明,那麼頭文件裏面放的天然就是關於函數,變量,類的「聲明」(對函數來講,也叫函數原型)了。記着,是「聲明」,不是「定義」。ip

      聲明和定義的區別。

複製代碼
    變量的聲明有兩種狀況:

    一、一種是須要創建存儲空間的。例如:int a 在聲明的時候就已經創建了存儲空間。

    二、另外一種是不須要創建存儲空間的。 例如:extern int a 其中變量a是在別的文件中定義的。

    聲明是向編譯器介紹名字--標識符。它告訴編譯器「這個函數或變量在某處可找到,它的模樣象什麼」。

    而定義是說:「在這裏創建變量」或「在這裏創建函數」。它爲名字分配存儲空間。不管定義的是函數仍是變量,編譯器都要爲它們在定義點分配存儲空間。 對於變量,編譯器肯定變量的大小,而後在內存中開闢空間來保存其數據,對於函數,編譯器會生成代碼,這些代碼最終也要佔用必定的內存。

    總之就是:把創建空間的聲明成爲「定義」,把不須要創建存儲空間的成爲「聲明」。

    基本類型變量的聲明和定義(初始化)是同時產生的;而對於對象來講,聲明和定義是分開的。

    例如:類A

    若是A a;就是一個聲明,告訴編譯器a是A類的一個對象變量,可是不進行初始化;

    若是之後a=new A();這就是初始化,分配了空間。

    (咱們聲明的最終目的是爲了提早使用,即在定義以前使用,若是不須要提早使用就沒有單獨聲明的必要,變量是如此,函數也是如此,因此聲明不會分配存儲空間,只有定義時纔會分配存儲空間。)

    用static來聲明一個變量的做用有二:

    (1)對於局部變量用static聲明,則是爲該變量分配的空間在整個程序的執行期內都始終存在。

    (2)外部變量用static來聲明,則該變量的做用只限於本文件模塊。


    補充:

    什麼是定義?什麼是聲明?它們之間的區別是什麼?

    所謂定義就是(編譯器)建立一個對象,爲這個對象分配一塊內存,並給它取上一個名字,這個名字就是就是咱們常常所說的變量名或對象名。

    聲明有2重含義:

    (1) 告訴編譯器,這個名字已經匹配到一塊內存上,下面的代碼用到變量或者對象是在別的地方定義的。聲明能夠出現屢次。

    (2) 告訴編譯器,這個名字已經被預約了,別的地方不再能用它來做爲變量名或對象名。

    定義和聲明的最重要區別就是:

    定義建立對象併爲這個對象分配了內存,聲明沒有分配內存。
複製代碼

 

 

因此,最好不要傻嘻嘻的在頭文件裏定義什麼東西。好比全局變量:
  /*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

  注意!!!

2、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」進行連接指定,這告訴編譯器,請保持個人名稱,不要給我生成用於連接的中間名。

 

3、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命令。

相關文章
相關標籤/搜索