上一講中大體介紹了變量的類型,不一樣類型的變量有不一樣的存儲類型、不一樣的生命週期、不一樣的做用域。這講介紹2個比較重要的關鍵字:static和extern。html
static和extern不只能夠用在變量上,還能夠用在函數上。這講先介紹它們對函數的做用。ios
在第三講和第四講中,我提到過一句話:若是一個程序中有多個源文件(.c),編譯成功會生成對應的多個目標文件(.obj),這些目標文件還不能單獨運行,由於這些目標文件之間可能會有關聯,好比a.obj可能會調用c.obj中定義的一個函數。將這些相關聯的目標文件連接在一塊兒後才能生成可執行文件。模塊化
先來理解2個概念:函數
外部函數:若是在當前文件中定義的函數容許其餘文件訪問、調用,就稱爲外部函數。C語言規定,不容許有同名的外部函數。spa
內部函數:若是在當前文件中定義的函數不容許其餘文件訪問、調用,只能在內部使用,就稱爲內部函數。C語言規定不一樣的源文件能夠有同名的內部函數,而且互不干擾。code
接下來就演示在一個源文件中調用另一個源文件定義的函數,好比在main.c中調用one.c中定義的one函數。orm
若是你想讓這個one函數能夠被main.c訪問,那麼one函數就必須是外部函數。完整的定義是要加上extern關鍵字。htm
不過這個extern跟auto關鍵字同樣廢,徹底能夠省略,由於默認狀況下,全部的函數就是外部函數。咱們能夠簡化一下:blog
怎樣才能調用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(這個是頭文件,之後調用該方法的時候直接包含該頭文件便可,就和OC裏邊的在頭文件裏邊先聲明方法,具體實現能夠在另外一個文件中實現相關方法)。之後,誰想調用這個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聲明一個內部函數
#include <stdio.h> static void test(); int main(int argc, const char * argv[]) { test(); return 0; } static void test() { printf("調用了test函數"); }
在第11行定義了一個test函數,這是一個內部函數,接着在第3行對test函數進行提早聲明,而後就能夠在第7行能夠調用test()函數了
* 在定義函數時,在函數的最左邊加上static能夠把該函數聲明爲內部函數(又叫靜態函數),這樣該函數就只能在其定義所在的文件中使用。若是在不一樣的文件中有同名的內部函數,則互不干擾。
* static也能夠用來聲明一個內部函數
* 在定義函數時,若是在函數的最左邊加上關鍵字extern,則表示此函數是外部函數,可供其餘文件調用。C語言規定,若是在定義函數時省略extern,則隱含爲外部函數。
* 在一個文件中要調用其餘文件中的外部函數,則須要在當前文件中用extern聲明該外部函數,而後就可使用,這裏的extern也能夠省略。
注:本文轉自M了個J的博客。