在C語言里面,有時候為了方便(方便的同義詞是偷懶),函數就直接在頭文件里面實現了。那么這樣子有什么問題呢?
下面舉個例子,這個例子只有3個文件
/* fun.h */
#ifndef FUN_H
#define FUN_H
void base(){};
void fun();
#endif
/* fun.c */
#include "fun.h"
void fun()
{
base();
}
/* main.c */
#include "fun.h"
int main()
{
fun();
return 0;
}
好,然后gcc一下
gcc -c main.c (通過)
gcc -c fun.c (通過)
gcc -o main main.o fun.o (鏈接錯誤)
出現錯誤...“base()函數重定義!”
為什么重定義呢?因為#include是預處理部分,在編譯之前由預處理程序在這個部分復制頭文件的內容過來。所以在編譯時候,main.o和fun.o文件都有base()函數的定義。那么鏈接程序就不知道鏈接那個定義好了(二義性啊)
如何解決呢,為了實現“聲明和實現分開”這個目標最好就是把這個base函數的函數體移到源文件里面。如果由於某種原因真的要放在頭文件中...也可以。
用static聲明就可以了,靜態函數的作用域是文件,而不是全局。比如,上面的例子將頭文件里面的void base(){}改成static void base(){},那就OK。
這個static在c語言中的用法可以google下,上面的資料好多很詳細滴。
順便說說頭文件的循環依賴的問題。
比如有三個頭文件a.h b.h c.h,a.h里面有#include "b.h",b.h里面有#include "c.h", c.h里面有#include "a.h",那就會造成文件的循環依賴,后果是什么呢?
比如有個文件a.c,上面有#include "a.h",那在a.c文件編譯之前,預處理程序就會不斷的把這三個頭文件的內容復制過來,超過了一定的數量,就會導致“頭文件數太多”的編譯錯誤。
解決方法呢,當然就是常見的#ifndef...#define...#endif組合了。不過要把前兩個寫在頭文件的開頭(一定是開頭),最后一個寫在最末尾。
這樣的話,第一次展開a.h b.h c.h的時候就已經定義了宏,到了c.h中的#include "a.h"時候,遇到了#ifndef,由於這個宏在上一次展開時已經定義了,所以這部分就跳過去了。也就是每個頭文件最多只在每個源文件里面包含一次。
但是即使編譯鏈接沒有問題,循環依賴也會降低開發效率,為什么?因為文件都在依賴,比如某一天,要改變a.h的一部分內容,然后所有依賴於a.h b.h c.h的文件都得重新編譯...鏈接;所以現在的C++有“前向聲明”的技巧可以緩解這個問題。(緩解並不是解決。)而JAVA運用的import機制就很好的解決了這個問題,真正實現了“實現與聲明相分離”這個目標。