前面兩節介紹的 C語言“僞類”和宏定義,在較大的項目中都是非常實用的。實際上,程序員的工作就是把一個較複雜的需求,分解成若幹個較獨立的模塊,然後繼續把每個模塊分解成若幹更簡單的工作,并編寫代碼逐個實現,最後再合并完成需求。在實際開發中,每一個獨立模塊單獨占用一個文件是合适的。本節将介紹 C 語言的多文件編程。
例如在某個項目中,text.c 負責處理文本,picture.c 負責處理圖片,video.c 負責處理視頻。整個項目的構成一目了然,維護很方便。還是像以前一樣,我們從實例出發:建立 fun.c 文件,自定義的函數都在此文件編寫。再建立 main.c 文件,main 函數編寫在此文件中,如下圖:
然後在 fun.c 文件裡定義 add 函數和全局變量 cnt:
// fun.c int cnt = 0; int add(int a, int b) { printf("add cnt: %d\n", cnt ); return a b; }
再在 main.c 文件裡的 main 函數中調用 add 函數:
// main.c #include <stdio.h> int main() { printf("3 4=%d\n", add(3, 4)); printf("3 7=%d\n", add(3, 7)); return 0; }
編譯執行,輸出結果如下。
雖然程序按照預期執行了,但是應該能在編譯時發現有警告信息:
...\hello\main.c|5|warning: implicit declaration of function 'add' [-Wimplicit-function-declaration]|
這是因為編譯器在處理 add 函數調用時沒有找到 add 函數的原型,隻能根據 add(3, 4) 函數調用“推測”隐式聲明:
int add(int, int);
所幸編譯器“推測”正确,因此程序得以正常執行。
C 語言編譯器為什麼需要函數原型?這是因為編譯器必須知道函數的參數類型和個數,以及返回值的類型,才能知道該生成什麼樣的指令。那,為什麼不從函數調用的隐式聲明中“推測”呢?這是因為并不是所有情況編譯器都能“推測”正确的,一旦編譯器“推測”錯誤,要麼程序無法編譯通過,要麼無法得到預期結果。
例如,add 函數的形參都是 int 型的,實際上我們也能傳入 char 型實參,這時編譯器就無法獲得正确的參數類型了。對于 printf 這種參數可變的函數,編譯器就更不能确定它的參數類型了。另外,函數的返回值類型,編譯器也往往無法正确獲得。既然如此,為什麼編譯器不自己去搜索函數的定義呢?因為編譯器不知道去哪裡找,例如我們在 main.c 裡調用的 add 函數在 fun.c 裡,編譯器又怎麼會知道呢?
extern 和 static 關鍵字extern 的字面意思是“外部的”,它也是 C 語言中的一個關鍵字,表示“外部符号”。我們已經知道 C 語言編譯器需要知道函數的原型,所以在 main.c 中,正确的做法應該是:
#include <stdio.h> extern int add(int a, int b); // 或 int add(int a, int b); int main() { char i = 7; printf("3 4=%d\n", add(3, 4)); printf("3 7=%d\n", add(3, i)); return 0; }
這樣編譯器就知道 add 函數的原型了,也知道它來自于外部文件。實際上,函數聲明中的 extern 也可以不寫,不過這裡就算不寫 extern 仍然表示 add 函數是外部符号。
應該注意到 fun.c 文件裡有個全局變量 cnt,我們能否在 main.c 中使用呢?答案是肯定的,使用 extern 引入定義就可以了。
extern int cnt;
引入外部變量時,extern 不能省略。extern int cnt; 不是定義變量,因此不會為 cnt 分配空間。另外,在 fun.c 中,我們可以把 cnt 初始化為 0 :
int cnt = 0;
而在 main.c 中,則不能對 cnt 做初始化,下面這種做法是非法的,編譯器會報錯:
extern int cnt = 1; //非法
引入全局變量 cnt 時,如果不寫 extern,意思就變了,int cnt;顯然表示定義了一個變量。
如果不希望外界使用本文件裡定義的函數,或者變量,該怎麼辦呢?答案是使用 static 關鍵字。以前我們使用過 static 來定義靜态變量,它其實還表示變量或者函數屬于“内部符号”,有 static 修飾的全局變量和函數在外部文件中都是不可見的。
// fun.c static int cnt = 0; static int add(int a, int b) { printf("add cnt: %d\n", cnt ); return a b; }
這時,cnt 和 add 函數隻能在 fun.c 文件中使用,在 main.c 中即使使用 extern 也是不能訪問 cnt 和 add 函數的。
有了 extern 和 static 關鍵字,我們在不同的文件裡定義不同的模塊時,就能方便的控制變量或者函數的訪問範圍了。
可是還有問題啊,如果 add 函數可以被外部訪問,還有 A 模塊也需要用到 add 函數,那我們就需要再在 A 模塊裡 extern add 函數。如此一來,以後隻要有模塊需要用到 add 函數,就需要 extern 一次,這不是太啰嗦了嗎?的确,所幸 C 語言中還有 頭文件,限于篇幅,接下來再介紹。
歡迎在評論區一起讨論,質疑。文章都是手打原創,每天最淺顯的介紹C語言,喜歡我的文章就關注一波吧,可以看到最新更新和之前的文章哦。
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!