在 C/C++ 專案開發中,隨著代碼量增長,將所有邏輯塞進單一 main.c 顯然不現實。我們習慣將功能模組拆分為獨立的 .c 文件(如 poker.c、player.c),但在早期階段,主文件往往通過 #include "poker.c" 直接包含實現文件。這種做法雖然能編譯通過,卻帶來嚴重弊端:被包含的文件無法在工程管理器中直觀顯示,修改時極不方便,且極易引發重複編譯與符號衝突。
頭文件(.h)專門存放 不產生內存佔用的聲明性代碼,包括:
#define)extern)原則上一個 .c 文件對應一個 .h 文件,而入口文件 main.c 通常不需要頭文件,因為它應只依賴其他模組,不被其他模組依賴。
#ifndef POKER_H
#define POKER_H
// 一副牌的張數
#define CARD_COUNT 54
// 花色枚舉
enum Suit { heart, spade, diamond, club, joker1, joker2 };
// 撲克牌結構體
typedef struct Card {
int value; // 點數(從 1 開始)
enum Suit suit; // 花色
} Card;
// 比較函數指針類型
typedef int (*COMPARE)(Card*, Card*);
// 外部變量聲明
extern Card pokers[CARD_COUNT];
// 函數聲明
void initOnePack();
char* getCardName(const Card*);
Card** shuffle(const Card*);
void sort(Card**, int, COMPARE);
int compare1(Card*, Card*);
int compare2(Card*, Card*);
#endif // POKER_H
#ifndef PLAYER_H
#define PLAYER_H
#include "poker.h" // 依賴 Card 類型定義
// 玩家結構體
typedef struct Player {
char name[64]; // 玩家名稱
Card** cards; // 手中的牌(指向原始牌陣列的指針,節省空間)
int cardsCount; // 手牌數量
} Player;
// 發牌函數聲明
void dispatchCards(Player**, int, const Card**);
#endif // PLAYER_H
player.h 使用了 Card 類型,故必須 #include "poker.h"。編譯器不會自動關聯同名的 .h/.c,必須顯式包含。main.c 改為包含頭文件 #include "poker.h"、#include "player.h",不再直接包含 .c 文件。poker.c、player.c 也要各自 #include 對應的頭文件,因為結構體定義已移至頭文件。當 main.c 包含 poker.h 和 player.h,而 player.h 又包含 poker.h 時,poker.h 實際上被包含了兩次。編譯器會報告結構體、枚舉、宏重複定義等錯誤。
利用預處理指令 #ifndef、#define、#endif 構建頭文件防衛符(Include Guard):
#ifndef POKER_H
#define POKER_H
// ... 所有聲明與定義 ...
#endif // POKER_H
原理:首次包含時 POKER_H 未定義,條件成立,定義宏並處理內容;再次包含時宏已存在,條件失效,內容被跳過。每個頭文件的宏名需唯一(通常用大寫文件名加 _H 後綴)。
經過重構,專案呈現清晰的分層依賴:
poker.h / poker.c:底層數據結構與算法,無外部依賴player.h / player.c:業務邏輯層,依賴 poker.hmain.c:應用入口,依賴兩個頭文件這樣不僅消除了重複定義錯誤,更建立了可維護、可擴展的模組化架構——這正是 C/C++ 工程化的基石。