1. 程式人生 > >extern與標頭檔案(*.h)的區別和聯絡

extern與標頭檔案(*.h)的區別和聯絡

個人認為有一些道理:所以轉過來學習了。

用#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" ([email protected]@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命令。

相關推薦

extern檔案(*.h)的區別聯絡

個人認為有一些道理:所以轉過來學習了。 用#include可以包含其他標頭檔案中變數、函式的宣告,為什麼還要extern關鍵字?         如果我想引用一個全域性變數或函式a,我只要直接在原始檔中包含#include<xxx.h> (xxx.h包含

檔案保護符#pragma once #pragma once #ifndef的區別

為了避免同一個檔案被include多次 1   #ifndef方式 2   #pragma once方式 在能夠支援這兩種方式的編譯器上,二者並沒有太大的區別,但是兩者仍然還是有一些細微的區別。     方式一:     #ifndef __SOMEFILE_H__     #define __SOMEFIL

hpp檔案h檔案區別

hpp,其實質就是將.cpp的實現程式碼混入.h標頭檔案當中,定義與實現都包含在同一檔案,則該類的呼叫者只需要include該hpp檔案即可,無需再將cpp加入到project中進行編譯。而實現程式碼將直接編譯到呼叫者的obj檔案中,不再生成單獨的obj,採用hpp將大幅度

C++學習筆記 — 理解檔案(.h)原始檔(.cpp)

原始檔根據#include來關聯檔案 系統自帶的檔案用尖括號括起來,編譯器會在系統檔案目錄下查詢 #include <> 使用者自定義的檔案用雙括號括起來,編譯器首先在使用者目錄下查詢,然

例項理解c++中向前宣告引用檔案區別

使用C++程式設計,編譯時經常出現這種錯誤"error: invalid use of incomplete type ‘class xxx’",或“error: forward declaration of ‘class xxx’”. 解決這種錯誤就是用理解c++中向前宣告與引用標頭檔案的

extern 用法,全域性變數檔案

用#include可以包含其他標頭檔案中變數、函式的宣告,為什麼還要extern關鍵字,如果我想引用一個全域性變數或 函式a,我只要直接在原始檔中包含#include<xxx.h> (xxx.h包含了a的宣告)不就可以了麼,為什麼還要用exter

原始檔檔案區別

標頭檔案和原始檔在本質上沒有任何區別。 只不過一般:字尾為 .h 的檔案是標頭檔案,內含函式宣告、巨集定義、結構體定義等內容。 字尾為 .c 的檔案是原始檔,內含函式實現,變數定義等內容。而且是什麼字尾也沒有關係,只不過編譯器會預設對某些字尾的檔案採取某些動作。這樣分開寫

檔案的編寫引用

      我用的是 Dev-c++  編寫標頭檔案     首先寫標頭檔案裡面的函式,       然後儲存,記得字尾寫  .h   就行   例:(順序表的標

noip的c++檔案操作檔案

看一些人寫這些真的心累 首先標頭檔案。 #include <bits/stdc++.h> using namespace std; 可以用!相信我,親測,說不能用的都不是好老師!其他都不要加,這一條包含了一切。 然後是檔案操作。 freopen(“題

C語言中自帶的檔案(.h)所包含的函式

由於之前沒有好好學習過C語言,所以對其自帶標頭檔案所包含的內容總是不清楚,每次寫程式碼都是盲目的#include很多.h,現在重新整理一下,發現了不少很好的函式,以方便複習查閱。 不完全統計,C語言標

關於函式實現在檔案(.h)中造成的一個問題

問題描述: 在專案中遇到一個問題,把一個比較簡短但是經常(包括其他檔案中的使用)使用的函式放在標頭檔案中實現,造成了一個連結錯誤。 <span style="font-family: Arial, Helvetica, sans-serif;">// file1

檔案重複包含變數重複定義

在c或c++中,標頭檔案重複包含問題是程式設計師必須避免的問題,也是很多新手容易犯錯的問題。為什麼要避免標頭檔案重複包含呢?        1.我們知道在編譯c或c++程式時候,編譯器首先要對程式進行預處理,預處理其中一項工作便是將你源程式中#include的標頭檔案完整的展

VS2015建立一個完整的c++工程:檔案.h 原始檔.cpp,自動生成類

開啟VS2015 ,新建VS win32工程,前面步驟很簡單,不再闡述 下面直接開始: 新建一個VC++ win32的程式, 在原始檔加下新增.cpp檔案,一般放main函式 #include <iostream> #include <locale&

關於c++裡面引用檔案尖括號雙引號的使用

今天寫資料庫,因為用的是mysql自帶的c api。設計到庫的匯入和標頭檔案的匯入。 用include 引用標頭檔案時,雙引號和尖括號的區別: 1.雙引號:引用非標準庫的標頭檔案,編譯器首先在程式原始檔所在目錄查詢,如果未找到,則去系統預設目錄查詢,通常用於引用使

NSIS文字及字串函式檔案介紹

轉自 http://hi.csdn.net/jinglidong 文字函式,顧名思義就是處理字串的函式。使用這些字串函式前,必須先包含標頭檔案WordFunc.nsh。該標頭檔案目前包含如下一些函式:WordFind、WordFind2X、WordFind3X、WordRe

預編譯檔案的作用使用方法介紹

預編譯頭的概念: 所謂的預編譯頭就是把一個工程中的那一部分程式碼,預先編譯好放在一個檔案裡(通常是以.pch為副檔名的),這個檔案就稱為預編譯標頭檔案這些預先編譯好的程式碼可以是任何的C/C++程式碼--------甚至是inline的函式,但是必須是穩定的,在工程開發的過

c++如何寫檔案.h

一、C++編譯模式 通常,在一個C++程式中,只包含兩類檔案——.cpp檔案和.h檔案。其中,.cpp檔案被稱作C++原始檔,裡面放的都是C++的原始碼;而.h檔案則被稱作C++標頭檔案,裡面放的也是C++的原始碼。 C+ +語言支援“分別編譯”(s

CMake2:版本號配置檔案生成

1.基本測試  最基本的功能就是利用原始碼檔案生成一個可執行程式。 CMakeLists.txt: cmake_minimum_required ( VERSION 3.5) project (T

C++ 檔案.h 原始檔.cpp

一、C++編譯模式 通常,在一個C++程式中,只包含兩類檔案——.cpp檔案和.h檔案。 其中,.cpp檔案被稱作C++原始檔,裡面放的都是C++的原始碼;而.h檔案則被稱作C++標頭檔案,裡面放的也是C++的原始碼。 C+ +語言

C語言 makefile檔案檔案的寫法。

makefile 檔案的書寫。 總結下make 執行過程       1)make 在當前目錄下找 "Makefile"或"makefile"的檔案     2)如果找到,則會找檔案中第一個目標檔案(target)。     3)如果 main 命令的執行,依賴後面命令執