將大型程序拆分為多個 .c/.cpp 源文件與 .h 頭文件,實現**模組化、封裝、並行開發、增量編譯**。
| 文件類型 | 內容 | 作用 | 是否編譯 |
|---|---|---|---|
頭文件 .h | **聲明**:函數原型、類型定義 (struct/enum/typedef)、宏、extern 變量聲明、inline 函數定義 | 接口契約,被 #include 到多個源文件 | **不**單獨編譯,隨包含它的源文件一起編譯 |
源文件 .c/.cpp | **定義/實現**:函數體、全局變量定義、靜態變量/函數 | 實現細節,編譯為目標文件 .o/.obj | **單獨編譯**,生成目標文件 |
.c 包含同一頭文件 → 鏈接時出現 multiple definition 錯誤。inline 函數、模板 (C++)、極簡常量可定義在頭文件。**關鍵誤區**:編譯器**不**負責跨文件找實現。流程如下:
#include "foo.h",將聲明注入 main.c,生成翻譯單元。main.c → main.o,編譯器看到聲明,假定實現存在別處,**不報錯**,只記錄符號 foo 未定義 (外部符號)。ld/link.exe) 將 main.o、foo.o、庫文件合併,**解析符號**:在 foo.o 中找到 foo 定義,填入 main.o 的調用處。**因此**:必須將所有 .c 文件加入構建系統 (Makefile/CMake/VS 項目/Code::Blocks 項目),鏈接器才能看到它們產生的 .o 文件。
project/
├── CMakeLists.txt
├── main.c
├── math.h
├── math.c
└── utils.h
utils.c
math.h (接口)
#ifndef MATH_H
#define MATH_H
int add(int a, int b);
int sub(int a, int b);
#endif
math.c (實現)
#include "math.h"
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
main.c (使用)
#include
#include "math.h"
int main() {
printf("%d\n", add(3, 4));
return 0;
}
CMakeLists.txt (構建腳本)
cmake_minimum_required(VERSION 3.10)
project(Demo C)
add_executable(demo main.c math.c utils.c)
| 錯誤現象 | 原因 | 解決 |
|---|---|---|
undefined reference to 'foo' | 聲明了但未鏈接實現 | 將 foo.c 加入構建目標 |
multiple definition of 'bar' | 頭文件放了變量/函數定義 | 頭文件用 extern int bar;,.c 中定義 int bar = 0; |
circular dependency | 頭文件互相包含 | 前向聲明、拆分接口、使用 #pragma once/防重復宏 |
| 修改 .h 導致全量重編譯 | .h 包含過多實現/內部細節 | 最小化頭文件依賴,使用前向聲明、Pimpl 惯用法 |
.c 編譯為 .o → ar rcs libfoo.a *.o (靜態庫) 或 gcc -shared -o libfoo.so *.o (動態庫)。gcc main.c -L. -lfoo -I.,鏈接器自動從庫中提取所需目標文件。掌握「聲明在 .h,定義在 .c,構建系統管鏈接」這條主線,多文件編程不再迷茫。