頭文件(.h/.hpp)與源文件(.c/.cpp)的分離是 C/C++ 專案工程化的基石。本文系統闡述「介面與實現分離」的核心原則、編譯模型運作機制、循環相依解決方案,並給出現代專案的目錄結構範例。
| 維度 | 頭文件 (.h) | 源文件 (.c/.cpp) |
|---|---|---|
| 核心目的 | 宣告介面(What) | 定義實現(How) |
| 典型內容 | 函式原型、型別定義、巨集、外部變數宣告、inline 函式 | 函式定義、靜態變數、靜態函式、實現細節 |
| 可見性 | 公開,被多個源文件 #include | 私有,僅編譯單元內可見 |
| 編譯處理 | 預處理器展開到每個包含它的 .c 檔 | 獨立編譯為 .obj/.o 目標檔 |
| 連結階段 | 不參與 | 連結器解析符號,合併為執行檔/庫 |
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
// 介面:只告訴調用者「有什麼」與「怎麼用」
int add(int a, int b);
int multiply(int a, int b);
double circle_area(double radius);
#endif // MATH_UTILS_H
#include "math_utils.h"
#include <math.h> // 實現細節:需要 M_PI
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
double circle_area(double radius) { return M_PI * radius * radius; }
#include <stdio.h>
#include "math_utils.h"
int main(void) {
printf("3 + 5 = %d\n", add(3, 5));
printf "圓面積 r=2: %.2f\n", circle_area(2.0));
return 0;
}
main.c ──gcc -c──► main.o
math_utils.c ──gcc -c──► math_utils.o
│
▼
gcc -o app main.o math_utils.o
│
▼
app (執行檔)
關鍵點:main.c 編譯時只看 math_utils.h 知道函式簽名;math_utils.c 編譯時實現細節;連結器負責把 main.o 對 add 的呼叫解析到 math_utils.o 的定義。
| 陷阱 | 現象 | 解決 |
|---|---|---|
| 頭文件重複包含 | 重複定義錯誤 | 防衛巨集 #ifndef FOO_H #define FOO_H ... #endif 或 #pragma once |
| 在頭文件定義變數/函式 | 多重定義連結錯誤 | 頭文件用 extern int g_var;,源文件 int g_var = 0; |
| 循環相依 (A.h include B.h, B.h include A.h) | 編譯錯誤/無限遞迴 | 前向宣告、拆分介面、介面隔離原則 |
| C++ 名稱修飾導致 C 連結失敗 | undefined reference | 頭文件用 extern "C" { ... } 包裹 C 介面 |
| 內聯函式定義在 .c | 其他檔案無法內聯 | 內聯函式放頭文件,或用 LTO (Link Time Optimization) |
project/ ├── include/ # 公開介面 (安裝到系統 /usr/local/include) │ └── mylib/ │ ├── api.h │ └── types.h ├── src/ # 內部實現 │ ├── api.c │ ├── internal.h # 內部私有介面 │ └── utils.c ├── test/ # 單元測試 │ └── test_api.c ├── CMakeLists.txt # 建構腳本 └── README.md
cmake_minimum_required(VERSION 3.10)
project(MyLib C)
add_library(mylib src/api.c src/utils.c)
target_include_directories(mylib
PUBLIC include # 介面:安裝給使用者
PRIVATE src # 實現:內部私有
)
add_executable(test_app test/test_api.c)
target_link_libraries(test_app mylib)
「頭文件 = 契約,源文件 = 履約」是 C/C++ 工程化的鐵律。嚴守介面與實現分離、善用防衛巨集、理解編譯單元與連結模型、採用現代建構系統管理相依,能讓專案從幾個檔案擴展到百萬行代碼依然結構清晰、編譯高效、維護低成本。這正是 C/C++ 在大型系統軟體領域長盛不衰的架構基因。