Back
Clang & Wasm 教學

檔案處理

檔案處理是 C 語言中讓程式與電腦儲存裝置互動的重要功能,可用來讀取或寫入文字與二進位檔案。透過函數操作,可實現資料的儲存與載入,適合用於設定檔、日誌記錄或資料儲存等應用。


1. 為什麼要檔案處理?

1.1 記憶體與檔案的差異

在 C 語言中,變數與資料結構的資料是儲存在 記憶體 (RAM) 中的。
但當程式結束執行時,所有記憶體中的資料都會被釋放、遺失

記憶體的特性:

  • 速度快
  • 暫時性的資料儲存
  • 程式結束後資料消失

檔案的特性:

  • 儲存在 磁碟中,即使電腦重開機資料仍在
  • 適合儲存長期資料,例如使用者帳號、設定檔、記錄檔等
  • 可讀寫大型資料集(ex: 文字檔、CSV、圖片檔)

1.2 為什麼不能只用變數?

假設你要寫一個簡單記事本程式:

char memo[1000];
printf("輸入筆記內容:");
scanf("%s", memo);

這樣做雖然可以暫時儲存資料,但當程式關閉、記憶體釋放後,資料就無法保存了。

使用檔案的方式可以這樣:

FILE *fp = fopen("memo.txt", "w");
fprintf(fp, "這是一段筆記內容");
fclose(fp);

下次開啟程式,可以從 memo.txt 讀取內容,達到「永久保存」的目的。


2. 檔案的基本操作:開啟與關閉

2.1 使用檔案前的第一步:開啟檔案

C 語言使用 fopen() 函式來開啟檔案,回傳一個 FILE * 指標,代表檔案的控制結構。

語法格式:

#include <stdio.h>
FILE *fopen(const char *filename, const char *mode);
  • "filename":字串型態,檔案名稱可包含路徑。
  • "mode":字串型態,指定檔案開啟的方式(讀/寫/附加…)。

常見模式:

模式 說明
"r" 讀取模式開啟,檔案必須存在
"w" 寫入模式開啟,檔案若存在會清空內容
"a" 附加模式開啟,從檔案末端開始寫入。

2.2 開啟檔案範例

#include <stdio.h>

int main() {
    FILE *fp = fopen("data.txt", "w"); // 開啟或建立檔案用於寫入
    if (fp == NULL) {
        printf("檔案開啟失敗!\n");
        return 1;
    }

    fprintf(fp, "Hello, file!\n"); // 寫入資料
    fclose(fp); // 關閉檔案
    return 0;
}

2.3 為什麼要關閉檔案?

使用完檔案後,一定要呼叫 fclose(),否則可能發生以下問題:

  • 資料無法正確寫入磁碟(緩衝區未刷新)
  • 佔用系統資源導致記憶體浪費或檔案損壞
  • 無法再次開啟該檔案進行其他操作

關閉語法:

fclose(fp);

2.4 錯誤處理的重要性

檔案開啟不一定會成功,例如:

  • 檔案不存在(讀取模式)
  • 權限不足(寫入模式)
  • 路徑錯誤、磁碟已滿

建議寫法:

FILE *fp = fopen("config.txt", "r");
if (fp == NULL) {
    perror("開啟檔案失敗");
    return 1;
}

perror() 會顯示錯誤訊息,有助於除錯。

2.5 Tips

  • fopen() 是進行所有檔案處理的起點,別忘了檢查是否開啟成功。
  • fclose() 是檔案使用的終點,幫助你釋放資源並避免資料遺失。

3. 寫入檔案的方法

當我們開啟檔案以 寫入模式 ("w""a") 開啟後,接下來就需要將資料寫入檔案中。

C 提供了多種方法來寫入資料,最常用的是:

  • fprintf():格式化寫入(類似 printf()
  • fputs():寫入整段文字(字串)

3.1fprintf:格式化寫入

用法:

#include <stdio.h>
int fprintf(FILE *stream, const char *format-string, argument-list);

類似 printf(),但會把輸出寫入檔案而不是螢幕。

範例:

FILE *fp = fopen("log.txt", "w");
if (fp != NULL) {
    int id = 1001;
    float score = 87.5;
    fprintf(fp, "學號:%d,成績:%.1f\n", id, score);
    fclose(fp);
}

使用場合:

  • 當需要輸出 變數資料、格式化結果 時。
  • 寫入結構化資料(例如 CSV 格式)非常適合。

3.2 fputs:字串寫入

用法:

#include <stdio.h>
int fputs(const char *string, FILE *stream);

直接將 整個字串 寫入檔案,不會自動換行。

範例:

FILE *fp = fopen("note.txt", "a");
if (fp != NULL) {
    fputs("這是寫入的第一行文字。\n", fp);
    fputs("這是第二行。\n", fp);
    fclose(fp);
}

使用場合:

  • 當你已經有完整字串(如輸入或常數)要寫入時。
  • 效率高、語法簡單,適合快速寫入整段內容。

3.3 fprintf vs fputs 比較

功能 fprintf fputs
是否可格式化 ✅ 可格式化輸出 ❌ 只能直接寫入字串
輸入類型 任意類型(int、float、char…) 僅限字串
換行處理 自行決定是否加 \n 自行決定是否加 \n
使用場景 輸出資料表格、變數 寫入文字段落或固定格式內容

Tips

  • fprintf 強大且彈性高,能夠輸出格式化資料。
  • fputs 簡單直覺,適合處理字串段落。
  • 合理選用寫入方式,能提升程式的可讀性與維護性。

4. 讀取檔案的方法

當程式需要「還原之前寫入的資料」時,就必須讀檔。常見情境:帳號驗證、成績查詢、設定讀取、資料分析…等。

4.1 兩種常見的讀檔方法

C 語言中,最常用的兩種讀檔方法:

方法 特性
fscanf 可格式化讀取,類似 scanf()
fgets 一次讀取一整行(字串)

4.2 EOF 檔案結尾

在 C 語言中,EOF 是一個代表 檔案結尾(End Of File) 的常數,用來表示檔案已經沒有更多資料可供讀取了。

EOF 的基本概念

  • EOF 是一個 整數常數,通常定義為 -1(在 stdio.h 中已經定義)。
  • 當使用像 fscanf()、fgets()、... 等函數從檔案讀取資料時,如果已經讀到檔案的尾端,就會返回 EOF。
  • 可以用來作為迴圈的條件,判斷是否繼續讀取資料。

輔助函數:feof()

若你不直接用 EOF,也可以用 feof(FILE *fp) 判斷是否到達檔案末尾:

if (feof(fp)) {
    printf("已經讀到檔案尾端。\n");
}

4.3 fscanf:格式化讀取

語法:

#include <stdio.h>
int fscanf(FILE *stream, const char *format, arg...);
  • stream:檔案指標(例如 fp)
  • format:格式字串(與 scanf() 用法相同)
  • arg...:儲存資料的變數指標(例如 &x, &y)

fscanf 的動作邏輯:

  • 目前串流位置開始讀取資料
  • 根據 format 字串指定的類型與順序來分析資料
  • 將讀取到的值儲存到對應的變數中(需使用指標)

範例:

假設 grades.txt 檔案內容如下:

Tom 90
Amy 85

程式範例:

FILE *fp = fopen("grades.txt", "r");
char name[20];
int score;

if (fp != NULL) {
    while (fscanf(fp, "%s %d", name, &score) != EOF) {
        printf("姓名:%s 分數:%d\n", name, score);
    }
    fclose(fp);
}

注意:

  • 必須確保資料格式與檔案內容一致。
  • 無法正確處理「整行字串」、「空白分隔資料」。

4.4 fgets:讀入整行字串

語法:

#include <stdio.h>
char *fgets (char *string, int n, FILE *stream);
  • str:存放讀取結果的字串陣列
  • n最多讀取的字元數(包含字串結尾的 \0
  • stream:資料來源(如檔案指標或 stdin

fgets 的動作邏輯:

fgets 會從目前「串流位置」開始,依以下條件停止讀取:

  1. 遇到「換行字元 \n
  2. 遇到「檔案結尾 EOF
  3. 已讀取了 n-1 個字元(保留一格給 \0

→ 最後會自動在字串最後補上結尾符號 \0

特別注意:

  • 若有讀到 \n,會保留在字串中
  • 如果 n == 1,代表你只給了一格空間(只能存 \0),因此返回的字串是空的
  • 若讀取失敗或遇到 EOF,fgets() 會回傳 NULL

範例:

char line[100];
FILE *fp = fopen("note.txt", "r");

if (fp != NULL) {
    while (fgets(line, sizeof(line), fp) != NULL) {
        printf("讀到一行:%s", line);  // 換行字元會印出來
    }
    fclose(fp);
}

4.5 選擇方法的時機

情況 適合使用方式
讀取格式固定的資料 fscanf
逐行分析或含空白內容的文字檔 fgets
有多種資料類型混合讀取時 先用 fgets 再用 sscanf

Tips

  • fscanf() 適合「精準格式」的讀取
  • fgets() 適合「整行」文字讀取,更有彈性
  • 熟悉兩者搭配,讓你讀檔技巧升級!

5. 常見的檔案模式解析

5.1 什麼是檔案模式?

當我們使用 fopen() 開啟檔案時,需要指定開啟模式(mode),決定我們對檔案的「存取權限與行為」。

5.2 基本三種模式

模式 功能 檔案存在時 檔案不存在時 允許動作
"r" 讀取 ✔️ 開啟現有檔案 ❌ 失敗 只讀
"w" 寫入 ✔️ 清空內容 ✔️ 建立新檔案 只寫
"a" 附加寫入 ✔️ 保留內容 ✔️ 建立新檔案 只寫(從尾端)

5.3 進階三種讀寫模式

模式 功能 檔案存在時 檔案不存在時 允許動作
"r+" 讀寫 ✔️ 開啟現有檔案 ❌ 失敗 讀+寫
"w+" 讀寫(重寫) ✔️ 清空內容 ✔️ 建立新檔案 讀+寫
"a+" 附加讀寫 ✔️ 保留內容 ✔️ 建立新檔案 讀+寫(從尾端)

5.4 各模式行為比較整理

模式 是否讀 是否寫 是否清空檔案 是否新增檔案 是否從檔案尾端寫入
"r"
"w"
"a"
"r+"
"w+"
"a+" ✅(寫入)

5.5 使用建議

情境 建議模式
只想讀舊檔 "r"
清空檔案重新寫入 "w"
保留原資料,新增新內容 "a"
要邊讀邊寫(不重設檔案內容) "r+"
需要重設並重新讀寫 "w+"
想讀舊資料並在尾端寫入 "a+"

Tips

  • 檔案模式決定程式能對檔案做什麼。
  • 精確選用開啟模式,可以避免檔案損毀或資料遺失。
  • 熟悉 "r""w""a" 與其加號變形,是寫好檔案處理的基礎。

6. 錯誤處理的重要性

6.1 為什麼要檢查錯誤?

  • 檔案可能不存在、權限不足、路徑錯誤...
  • 若不處理錯誤,程式可能會:
    • 異常終止(crash)
    • 操作空指標(未定行為)
    • 寫入錯誤資料

6.2 fopen() 的失敗檢查

FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
    printf("無法開啟檔案!\n");
}
  • fopen() 回傳失敗時會回傳 NULL
  • 務必檢查指標是否為 NULL 才繼續操作

6.3 使用 perror() 顯示錯誤原因

FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
    perror("fopen 失敗");
}
  • perror():根據 errno 印出對應錯誤訊息
  • 可以快速幫助你知道錯在哪

📌 範例輸出(若檔案不存在):

fopen 失敗: No such file or directory

6.4 常見錯誤碼 errno(進階)

錯誤碼 說明
ENOENT 檔案不存在
EACCES 權限不足
EISDIR 嘗試開啟目錄當作檔案

(可搭配 <errno.h>strerror(errno) 使用)

7. 實作練習:學生資料管理系統

7.1 功能簡介

這是一個小型系統,支援:

  1. 寫入學生資料到檔案中
  2. 讀取檔案並列出所有學生資料
  3. 處理檔案錯誤(如檔案不存在)

7.2 資料結構定義

typedef struct {
    char name[20];
    int score;
} Student;

7.3 主要功能實作

✏️ 寫入資料

void saveStudent(Student s) {
    FILE *fp = fopen("students.txt", "a");
    if (fp == NULL) {
        perror("寫入失敗");
        return;
    }
    fprintf(fp, "%s %d\n", s.name, s.score);
    fclose(fp);
}

✏️ 讀取資料

void readStudents() {
    FILE *fp = fopen("students.txt", "r");
    if (fp == NULL) {
        perror("讀取失敗");
        return;
    }
    Student s;
    while (fscanf(fp, "%s %d", s.name, &s.score) != EOF) {
        printf("學生:%s,成績:%d\n", s.name, s.score);
    }
    fclose(fp);
}

7.4 主選單簡易控制

int main() {
    int choice;
    Student s;
    
    do {
        printf("\n1. 新增學生資料\n2. 顯示所有學生\n0. 離開\n請選擇:");
        scanf("%d", &choice);
        getchar(); // 清除換行

        switch (choice) {
            case 1:
                printf("請輸入學生姓名:");
                fgets(s.name, sizeof(s.name), stdin);
                s.name[strcspn(s.name, "\n")] = '\0'; // 去除換行
                printf("請輸入成績:");
                scanf("%d", &s.score);
                saveStudent(s);
                break;
            case 2:
                readStudents();
                break;
        }
    } while (choice != 0);
    
    return 0;
}

7.5 學習重點整理

  • 使用 fopen()fprintf()fscanf() 操作資料
  • 學會 perror() 做基本錯誤提示
  • 熟悉文字檔資料的基本格式處理
SNPQ © 2025