extern、定義和聲明

Extern的問題在於不知道這個關鍵詞出現的時候到底是聲明還是定義。

謹記:聲明可以多次,定義只能一次。

    對於變量來說,定義就是聲明.

   例如:  int a;  我們可是說它是定義也可以說它是聲明。

   但是對於函數來說定義和聲明完全不是一回事 。

    void   sum(int a,int b);這是函數的聲明

    void   sum(int a,int b)

    {

    }

    整體是函數的定義  ,函數的定義沒有分號 而且要加上一對花括號 ,裏邊是函數的實現。

   函數一定要在定義前聲明否則會報錯 。我一般在主函數前寫上函數的聲明 ,然後在主函數之後寫函數的定義。

   當然 如果你用的函數是API已經封裝好的  比如使用MFC裏的open()打開文件或者文件夾函數時 ,就不需要聲明  直接定義就可以了。
 

 demo:(大部分人看下demo應該就能理解)

環境:vc++6.0

運行結果(運行file2):

函數的聲明extern關鍵詞是可有可無的,因爲函數本身不加修飾的話就是extern的。但是引用的時候一樣是需要聲明的。

 

全局變量在外部使用聲明時,extern關鍵詞是必須的,如果變量無extern修飾且沒有顯式的初始化,同樣成爲變量的定義,因此此時必須加extern,而編譯器在此標記存儲空間在執行時加載如內存並初始化爲0。

局部變量的聲明不能有extern的修飾,且局部變量在運行時纔在堆棧部分分配內存。

 

 

引用性聲明、定義性聲明

 

強符號、弱符號

出現在linux的gcc鏈接分析中,可以加深鏈接的理解。

全局變量或函數本質上講沒有區別,函數名是指向函數二進制塊開頭處的指針。而全局變量是在函數外部聲明的變量。函數名也在函數外,因此函數也是全局的。

 

在使用中,要形成一種風格。

 

 

 

頭文件

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

我做過一個實驗,將頭文件的後綴改成xxx.txt,然後在引用該頭文件的地方用

#include"xxx.txt"

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

不管是C還是C++,你把你的函數,變量或者結構體,類啥的放在你的.c或者.cpp文件裏。然後編譯成lib,dll,obj,.o等等,然後別人用的時候最基本的gcc hisfile.cpp yourfile.o|obj|dll|lib 等等。
但對於我們程序員而言,他們怎麼知道你的lib,dll...裏面到底有什麼東西?要看你的頭文件。你的頭文件就是對用戶的說明。函數,參數,各種各樣的接口的說明。
    那既然是說明,那麼頭文件裏面放的自然就是關於函數,變量,類的“聲明”了。記着,是“聲明”,不是“定義”。
那麼,我假設大家知道聲明和定義的區別。所以,最好不要傻嘻嘻的在頭文件裏定義什麼東西。比如全局變量:

#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居然可以被省略,所以會讓你搞不清楚到底是聲明還是定義,下面分變量和函數兩類來說:

(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;這是一個定義,不是聲明。

 

(2)函數
     函數,函數,對於函數也一樣,也是定義和聲明,定義的時候用extern,說明這個函數是可以被外部引用的,聲明的時候用extern說明這是一個聲明。 但由於函數的定義和聲明是有區別的,定義函數要有函數體,聲明函數沒有函數體,所以函數定義和聲明時都可以將extern省略掉,反正其他文件也是知道這個函數是在其他地方定義的,所以不加extern也行。兩者如此不同,所以省略了extern也不會有問題。
    比如:

int fun(void)
{
return 0;
}

很好,我們定義了一個全局函數

int fun(void);
我們對它做了個聲明,然後後面就可以用了
加不加extern都一樣
我們也可以把對fun的聲明 放在一個頭文件裏,最後變成這樣

int fun(void);//函數聲明,所以省略了extern,完整些是extern int fun(void);

int fun(void)
{
return 0;
}//一個完整的全局函數定義,因爲有函數體,extern同樣被省略了。
然後,一個客戶,一個要使用你的fun的客戶,把這個頭文件包含進去,ok,一個全局的聲明。沒有問題。
但是,對應的,如果是這個客戶要使用全局變量,那麼要extern 某某變量;不然就成了定義了。

總結下:

 

對變量而言,變量的聲明有兩種情況: 一種是需要建立存儲空間的,不用加extern;2、另一種是不需要建立存儲空間,需要加extern 。如果你想在本源文件中使用另一個源文件的變量,就需要在使用前用extern聲明該變量,或者在頭文件中用extern聲明該變量;

對函數而言,如果你想在本源文件中使用另一個源文件的函數,就需要在使用前用聲明該函數,聲明函數加不加extern都沒關係,所以在頭文件中函數可以不用加extern。

      

 

extern "C"的用法 
鏈接指示符extern C
    如果程序員希望調用其他程序設計語言尤其是C 寫的函數,那麼調用函數時必須告訴編譯器使用不同的要求,例如當這樣的函數被調用時函數名或參數排列的順序可能
不同,無論是C++函數調用它還是用其他語言寫的函數調用它,程序員用鏈接指示符linkage directive 告訴編譯器該函數是用其他的程序設計語言編寫的,鏈接指示符有兩種形式既可以是單一語句single statement 形式也可以是複合語句compound statement 形式。
// 單一語句形式的鏈接指示符
extern "C" void exit(int);
// 複合語句形式的鏈接指示符
extern "C" {
int printf( const char* ... );
int scanf( const char* ... );
}
// 複合語句形式的鏈接指示符
extern "C" {
#include <cmath>
}
    鏈接指示符的第一種形式由關鍵字extern 後跟一個字符串常量以及一個普通的函數,聲明構成雖然函數是用另外一種語言編寫的但調用它仍然需要類型檢查例如編譯器會檢查傳遞給函數exit()的實參的類型是否是int 或者能夠隱式地轉換成int 型,多個函數聲明可以用花括號包含在鏈接指示符複合語句中,這是鏈接指示符的第二種形式花擴號被用作分割符表示鏈接指示符應用在哪些聲明上在其他意義上該花括號被忽略,所以在花括號中聲明的函數名對外是可見的就好像函數是在複合語句外聲明的一樣,例如在前面的例子中複合語句extern "C"表示函數printf()和scanf()是在C 語言中寫的,函數因此這個聲明的意義就如同printf()和scanf()是在extern "C"複合語句外面聲明的一樣,當複合語句鏈接指示符的括號中含有#include 時在頭文件中的函數聲明都被假定是用鏈接指示符的程序設計語言所寫的在前面的例子中在頭文件<cmath>中聲明的函數都是C函數鏈接指示符不能出現在函數體中下列代碼段將會導致編譯錯誤。
int main()
{
// 錯誤: 鏈接指示符不能出現在函數內
extern "C" double sqrt( double );
305 第七章函數
double getValue(); //ok
double result = sqrt ( getValue() );
//...
return 0;
}
如果把鏈接指示符移到函數體外程序編譯將無錯誤
extern "C" double sqrt( double );
int main()
{
double getValue(); //ok
double result = sqrt ( getValue() );
//...
return 0;
}
    但是把鏈接指示符放在頭文件中更合適在那裏函數聲明描述了函數的接口所屬,如果我們希望C++函數能夠爲C 程序所用又該怎麼辦呢我們也可以使用extern "C"鏈接指示符來使C++函數爲C 程序可用例如。
// 函數calc() 可以被C 程序調用
extern "C" double calc( double dparm ) { /* ... */ }
    如果一個函數在同一文件中不只被聲明一次則鏈接指示符可以出現在每個聲明中它,也可以只出現在函數的第一次聲明中在這種情況下第二個及以後的聲明都接受第一個聲
明中鏈接指示符指定的鏈接規則例如
// ---- myMath.h ----
extern "C" double calc( double );
// ---- myMath.C ----
// 在Math.h 中的calc() 的聲明
#include "myMath.h"
// 定義了extern "C" calc() 函數
// calc() 可以從C 程序中被調用
double calc( double dparm ) { // ...
    在本節中我們只看到爲C 語言提供的鏈接指示extern "C",extern "C"是惟一被保證由所有C++實現都支持的,每個編譯器實現都可以爲其環境下常用的語言提供其他鏈接指示例如extern "Ada"可以用來聲明是用Ada 語言寫的函數,extern "FORTRAN"用來聲明是用FORTRAN 語言寫的函數,等等因爲其他的鏈接指示隨着具體實現的不同而不同所以建議讀者查看編譯器的用戶指南以獲得其他鏈接指示符的進一步信息。

 

總結 extern “C”

       extern “C” 不但具有傳統的聲明外部變量的功能,還具有告知C++鏈接器使用C函數規範來鏈接的功能。 還具有告知C++編譯器使用C規範來命名的功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章