咱們知道,不一樣類型的變量有不一樣的存儲類型、不一樣的生命週期、不一樣的做用域。這講介紹2個比較重要的關鍵字:
static
和extern
。static和extern不只能夠用在變量上,還能夠用在函數上。html
C語言根據變量的存儲類型的不一樣,能夠把變量分爲:
自動變量、
靜態變量、
寄存器變量`。自動變量
是存儲在堆棧
中的。ios
靜態變量
是存儲在靜態內存
中的,也就是不屬於堆棧。模塊化
哪些是靜態變量:函數
全部的全局變量
都是靜態變量
spa
被關鍵字static
修飾的局部變量也是靜態變量
code
生命週期:靜態變量在程序運行以前建立,在程序的整個運行期間始終存在,直到程序結束。htm
咱們知道:若是一個程序中有多個源文件(.c),編譯成功會生成對應的多個目標文件(.obj),這些目標文件還不能單獨運行,由於這些目標文件之間可能會有關聯,好比a.obj可能會調用c.obj中定義的一個函數。將這些相關聯的目標文件連接在一塊兒後才能生成可執行文件。blog
先來理解2個概念:生命週期
外部
函數:若是在當前文件中定義的函數容許
其餘文件訪問、調用,就稱爲外部
函數。C語言規定,不容許有同名的外部函數。ip
內部
函數:若是在當前文件中定義的函數不容許
其餘文件訪問、調用,只能在內部使用,就稱爲內部
函數。C語言規定不一樣的源文件能夠有同名的內部函數,而且互不干擾。
接下來就演示在一個源文件中調用另一個源文件定義的函數,好比在main.c中調用one.c中定義的one函數。
若是你想讓這個one函數能夠被main.c訪問,那麼one函數就必須是外部
函數。完整的定義是要加上extern
關鍵字。
不過這個extern跟auto關鍵字同樣廢,徹底能夠省略
,由於默認狀況下,全部的函數就是外部函數。咱們能夠簡化一下:
怎樣才能調用one.c中的one函數呢?你可能會產生2個想法:
想法1:直接在main函數中寫上one();
這個作法確定不行
,由於main函數根本不知道one函數的存在,怎麼調用呢?這個在標準C編譯器裏面會報錯的,可是在Xcode中只是個警告。
想法2:在main.c中包含one.c文件
你們都知道#include的做用純粹就是內容拷貝,因此又至關於
哎,這麼一看好像是對的哦,在main函數前面定義了個one函數,而後在main函數中調用了這個one函數。從語法上看是對的,因此編譯是沒問題的。可是這個程序不可能運行成功
,由於在連接的時候會報錯。咱們已經在one.c中定義了one函數,如今又在main.c中定義one函數,C語言規定不容許有同名的外部函數,連接的時候連接器會發如今one.obj和main.obj中定義了同一個函數,會直接報錯,Xcode中的錯誤信息是這樣的:
duplicate symbol _one是說one這個標識符重複了,linker是指連接器。
上面的2種想法都是不可行的,其實思路是一致的:讓main函數知道one函數的存在。正確的作法應該是在main函數前面對one函數進行提早聲明(看清楚,是聲明,不是定義,定義和聲明是兩碼事)
。
你想要把其餘源文件中定義的外部
函數拿過來聲明,完整的作法,應該使用extern
關鍵字,表示引用別人的"外部函數"
運行程序,從控制檯輸出能夠發現 "one.c中定義的one函數" 已經被 "main.c的main函數" 成功調用了。
也有人可能會立刻冒出一個想法:假如除開one.c,還有其餘源文件也有定義這個one函數怎麼辦?那main函數調用的到底是誰的one函數啊?放心,絕對不會有這種狀況,剛纔不是說了麼,不容許重複定義同一個外部函數,否則連接器會報錯的,因此只會有一個外部one函數。
上述就是extern
關鍵字對函數的做用:用來定義和聲明一個外部函數。其實extern又跟auto同樣廢,徹底能夠省略。因而,咱們能夠簡化成這樣:
爲了模塊化地開發,在正規的項目裏面,咱們會把one函數的聲明寫到另外一個頭文件中,固然,這個頭文件的命名最好有意義、規範一點,好比叫one.h。之後,誰想調用這個one函數,包含one.h這個頭文件就好了。因而最後的代碼結構是這樣的:
從上面的例子能夠看出,one.c中定義的one函數是能夠被其餘源文件訪問的。其實有時候,咱們可能想定義一個"內部函數",也就是不想讓其餘文件訪問本文件中定義的函數。這個很是簡單,你只須要在定義函數的時候加個static
關鍵字便可。
(咱們就在上面例子的代碼基礎上進行修改)
我在void one()的前面加了個static,表明one函數是個內部函數。
而後你會發現程序運行不起來了,在連接的時候就報錯了。報錯的緣由很簡單:咱們在main.c中調用了one.c中定義的one函數,可是如今one.c的one函數是個"內部函數",不容許其餘文件訪問。咱們來看看錯誤信息:
第1個紅框中的Undefined symbols...意思是one這個標識符沒有被定義,也就是找不到one;第2個紅框的linker代表是連接器報錯了。
但這個程序是能夠編譯成功的,由於咱們在main函數前面聲明瞭one函數(函數的聲明和定義是兩碼事),這個函數聲明能夠理解爲:在語法上,騙一下main函數,告訴它one函數是存在的,因此從語法的角度上main函數是能夠調用one函數的。究竟這個one函數存不存在呢,有沒有被定義呢?編譯器是無論的。在編譯階段,編譯器只會檢測單個源文件的語法合不合理,並不檢測函數有沒有定義,只有在連接的時候纔會檢測這個函數存不存在,也就是有沒有被定義。
咱們再來討論一個問題,爲何好多狀況下都是能夠成功編譯,可是連接的時候報錯呢?只要你理解編譯和連接的做用就好辦了。
所謂
編譯
,就是單獨檢查每一個源文件的語法是否合理,並不會檢查每一個源文件之間的關聯關係,一個源文件編譯成功就生成一個目標文件。
所謂
連接
,就是檢查目標文件的關聯關係,將相關聯的目標文件組合在一塊兒,生成可執行文件。
看完這2個概念,再回去思考下前面報的錯,應該能夠徹底明白了。
咱們還能夠用static聲明一個內部函數
1 #include <stdio.h> 2 3 static void test(); 4 5 int main(int argc, const char * argv[]) 6 { 7 test(); 8 return 0; 9 } 10 11 static void test() { 12 printf("調用了test函數"); 13 }
在第11行定義了一個test函數,這是一個內部函數,接着在第3行對test函數進行提早聲明,而後就能夠在第7行能夠調用test()函數了
在定義函數時,在函數的最左邊加上static
能夠把該函數聲明爲內部函數
(又叫靜態函數),這樣該函數就只能在其定義所在的文件中使用。若是在不一樣的文件中有同名的內部
函數,則互不干擾。
static也能夠用來聲明一個內部函數
在定義
函數時,若是在函數的最左邊加上關鍵字extern
,則表示此函數是外部函數,可供其餘文件調用。C語言規定,若是在定義函數時省略extern
,則隱含爲外部函數。
在一個文件中要調用其餘文件中的外部函數,則須要在當前文件中用extern聲明
該外部
函數,而後就可使用,這裏的extern也能夠省略。
前面講到,你在一個源文件中不管寫多少遍全局變量int a;,它們表明的都是同一個變量。還有一個事實,假如在另外一個源文件中也有全局變量int a;,那麼這兩個源文件的全部全局變量int a;都表明着同一個變量。extern關鍵字仍是適用的,好比:
和
或者是:
和
上面的兩種狀況下,test.c和main.c中使用的全局變量a都仍是表明着同一個變量
注意了,不能夠兩個文件的全部所有變量a都用extern,下面的作法是錯誤的:
和
由於extern
是用來聲明
一個已經定義過的變量
,這兩個文件都是在聲明變量,沒有人定義變量,在連接的時候確定報錯:
大體錯誤意思是:標示符a未定義
但不少時候,咱們並不想讓源文件中的全局變量跟其餘源文件共享,至關於私有的全局變量,那麼你就得用static關鍵字來定義變量。
這樣寫完,test.c和main.c的變量a分別表明着不一樣的變量,它們是沒有聯繫的、互不干擾的。也就是說,main.c沒法訪問test.c中的變量a,所以在main.c中將a修改成10後,test.c中的a依然爲0。輸出結果:
。
其實static還能夠用來修飾局部變量。
1.extern能夠用來聲明
一個全局變量,可是不能用來定義變量
2.默認狀況下,一個全局變量是能夠供多個源文件共享的,也就說,多個源文件中同名的全局變量都表明着同一個變量
3.若是在定義全局變量的時候加上static關鍵字,此時static的做用在於限制該全局變量的做用域,只能在定義該全局變量的文件中才能使用,跟其餘源文件中的同名變量互不干擾
注:本文轉自李明傑老師的博文。