檔案處理
檔案處理是 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 會從目前「串流位置」開始,依以下條件停止讀取:
- 遇到「換行字元
\n
」 - 遇到「檔案結尾 EOF」
- 已讀取了 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 功能簡介
這是一個小型系統,支援:
- 寫入學生資料到檔案中
- 讀取檔案並列出所有學生資料
- 處理檔案錯誤(如檔案不存在)
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()
做基本錯誤提示 - 熟悉文字檔資料的基本格式處理