C 語言模組化程式設計 (.h 與 .c 檔案)

當程式越來越大時,將所有程式碼都塞在一個 main.c 裡會變成一場災難。為了好維護、好分工,我們會將程式拆成多個檔案。這就是模組化 (Modular Programming)

為什麼要分檔?

  1. 可讀性:將相關的功能分門別類(例如將學生相關函式放在 student.c,數學相關放在 math_utils.c)。
  2. 重用性:寫好的模組可以在其他專案中重複使用。
  3. 編譯速度:修改某個檔案時,只需要重新編譯該檔案,不用整個專案重編(這在大型專案非常重要)。

.h 與 .c 的分工

在 C 語言中,我們通常會將一個模組拆成兩個檔案:

  • 標頭檔 Header File (.h)

    • 用途:對外公開的「介面」或「說明書」。
    • 內容:函式原型 (Prototypes)、結構定義 (Struct Definitions)、巨集 (Macros)、extern 變數宣告。
    • 原則只放宣告 (Declaration),不放實作 (Definition)
  • 原始碼檔案 Source File (.c)

    • 用途:實際的「實作」或「內容」。
    • 內容:函式的具體程式碼、全域變數的定義。
    • 原則:必須 #include 對應的 .h 檔,以確保宣告與實作一致。

實戰範例:Math 工具箱

假設我們要寫一個簡單的數學模組。

1. 建立標頭檔 my_math.h

// my_math.h
#ifndef MY_MATH_H
#define MY_MATH_H

// 宣告函式原型
int add(int a, int b);
int sub(int a, int b);

#endif

Include Guard (防護罩)

#ifndef MY_MATH_H
#define MY_MATH_H
...
#endif

這三行非常重要!它是為了避免同一個 .h 檔被重複 include 而導致編譯錯誤。

2. 建立實作檔 my_math.c

// my_math.c
#include "my_math.h" // 引用自己的標頭檔

// 實作函式
int add(int a, int b) {
    return a + b;
}

int sub(int a, int b) {
    return a - b;
}

3. 主程式 main.c

// main.c
#include <stdio.h>
#include "my_math.h" // 引用標頭檔,這樣 main 才知道 add 和 sub 長怎樣

int main() {
    printf("10 + 20 = %d\n", add(10, 20));
    printf("10 - 20 = %d\n", sub(10, 20));
    return 0;
}

編譯多個檔案

由於現在我們有多個 .c 檔,編譯時必須將它們一起編譯。

# 同時編譯主程式和模組
gcc main.c my_math.c -o my_program

# 執行
./my_program

extern 與 static 關鍵字

在多檔案開發中,這兩個關鍵字很重要:

  • extern:告訴編譯器「這個變數是在別的檔案定義的,請去別的地方找」。通常用於 .h 檔中宣告全域變數。
  • static
    • 用在全域變數或函式前:表示這個變數/函式只能在這個 .c 檔內被看到(私有化),避免與其他檔案的變數名稱衝突。

建立標頭檔 shared_data.h

#ifndef SHARED_DATA_H
#define SHARED_DATA_H

// extern: 宣告一個全域變數,告訴編譯器這個變數在別的檔案中定義
extern int global_counter;

// 宣告一個函式原型
void increment_and_print_counter();

#endif

建立實作檔 shared_data.c

#include <stdio.h>
#include "shared_data.h"

// 定義 global_counter,這是它實際分配記憶體的地方
int global_counter = 0;

// static: 宣告一個靜態函式,它只能在 shared_data.c 這個檔案內被呼叫
static void _internal_log_message(const char* msg) {
    printf("[Internal] %s\n", msg);
}

void increment_and_print_counter() {
    _internal_log_message("Incrementing counter...");
    global_counter++;
    printf("Counter in shared_data.c: %d\n", global_counter);
}

建立主程式 main.c

#include <stdio.h>
#include "shared_data.h" // 引入標頭檔,才能使用 extern 宣告的變數和函式

// 這裡不能直接呼叫 _internal_log_message(),因為它是 static 的,只在 shared_data.c 內部可見

int main() {
    printf("Initial global_counter from main.c: %d\n", global_counter); // 使用 extern 變數

    global_counter = 10; // 修改 extern 變數
    printf("Modified global_counter from main.c: %d\n", global_counter);

    increment_and_print_counter(); // 呼叫 shared_data.c 中的函式
    increment_and_print_counter();

    printf("Final global_counter from main.c: %d\n", global_counter);

    return 0;
}

掌握模組化設計,是從寫「作業」進階到寫「專案」的關鍵一步!