介面(標頭檔)與實作(函式庫)之別
一個函式庫有兩個扮演迥異角色的部分。介面是承諾——你可以呼叫的函式清單,連同它們的名稱、參數與回傳型別——它住在標頭檔裡。實作是兌現——真正做事的已編譯機器碼——它住在函式庫檔裡。餐廳的菜單(介面)告訴你能點什麼、要多少錢;廚房(實作)才真正把它煮出來,而你從不會看到裡頭。
具體來說,當你寫 #include <foo.h>,你拉進來的是宣告:像「int foo_add(int a, int b);」這樣的行,告訴編譯器有這麼一個函式存在、它收什麼引數、回傳什麼——但不告訴它怎麼運作。這對編譯器來說已足以檢查你的呼叫、並產生呼叫 foo_add 的程式碼。「怎麼做」則另存於已編譯的函式庫(libfoo.a 或 libfoo.so),由連結器之後接上。標頭檔是契約;函式庫是本體。它們相互對應卻彼此分立,而你可以只把標頭檔交給使用者,完全不揭露背後的原始碼。
為什麼重要:這個分離正是讓你只需知道一個函式庫的介面就能使用它的原因,而它的作者只要契約不變,就能自由重寫實作。它也解釋了一對常見的錯誤:引入了標頭卻忘了連結函式庫,你會得到連結器的「undefined reference」(介面找到了,實作缺失);連結了函式庫卻忘了標頭,編譯器就不知道那個函式的簽章。兩半都需要,而它們以不同、可辨認的方式各自失敗。
foo.h (介面): int foo_add(int a, int b); // 只有宣告 foo.c (實作): int foo_add(int a, int b){ return a + b; } 使用者以 #include "foo.h" 來呼叫 foo_add,並連結 libfoo 以取得程式碼。
標頭檔宣告你能呼叫什麼;已編譯的函式庫提供它實際上如何完成。
引入標頭檔不等於連結函式庫:標頭檔給編譯器那個函式的形狀,但少了已編譯的實作,連結器仍會以「undefined reference」失敗。介面與實作各自獨立,兩者都不可缺。