文件操作是C語言程式設計中的核心技能之一。無論是數據持久化、配置文件讀取,還是日誌記錄,都離不開文件的讀寫。本文將系統梳理C語言標準庫提供的文件操作函數,並通過完整的代碼示例演示各種讀寫模式的實際應用。
在深入代碼之前,先建立一個清晰的函數分類認知:
fgetc / fputc - 單字符讀寫fgets / fputs - 按行讀寫字符串fread / fwrite - 二進制數據塊讀寫,適合結構體數組fscanf / fprintf - 類似scanf/printf的文件版本fopen的模式字符串組合決定了文件的訪問方式:
| 模式 | 含義 | 文件存在 | 文件不存在 |
|---|---|---|---|
| r | 只讀 | 打開 | 失敗返回NULL |
| w | 只寫 | 截斷長度為0 | 創建新文件 |
| a | 追加 | 定位到文件末尾 | 創建新文件 |
| r+ | 讀寫 | 打開 | 失敗 |
| w+ | 讀寫 | 截斷 | 創建 |
| a+ | 讀寫追加 | 打開,定位末尾 | 創建 |
加上b表示二進制模式(如rb、wb+),在Windows下處理換行符轉換時尤為重要。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
/*
* 關鍵常量與函數原型說明:
* EOF = -1,表示文件結束或錯誤
* fgets最多讀取 size-1 個字符,遇換行或EOF停止
* fputs寫入時不包含字符串結束符'\0'
* fread/fwrite返回實際讀寫的項數,若不等於預期count則可能出錯或到達EOF
*/
FILE *fp = NULL;
/* === 讀取函數族 === */
/* 字符讀取:逐個讀取,適合簡單文本處理 */
void GetCharFromFile(FILE *fp) {
int ch;
while ((ch = fgetc(fp)) != EOF) {
printf("fgetc: [%c]\n", ch);
}
}
/* 行讀取:緩衝區讀取,保留換行符,高效處理文本行 */
void GetLineFromFile(FILE *fp) {
char line[1024];
while (fgets(line, sizeof(line), fp) != NULL) {
printf("fgets: [%s]", line); // line已包含換行符
}
}
/* 塊讀取:二進制讀取,適合結構體數組 */
void GetBlockFromFile(FILE *fp) {
int block[10];
int count = 1;
while (fread(block, sizeof(block), count, fp) == count) {
printf("fread: ");
for (int i = 0; i < 10; i++) printf("%d ", block[i]);
printf("\n");
}
}
/* 格式化讀取:按指定格式解析,返回成功讀取的項數 */
void ReadFormatToFile(FILE *fp) {
char file[32], func[32], date[32];
int line;
if (fscanf(fp, "%s %s %d %[^\n]", file, func, &line, date) == 4) {
printf("file: %s\nfunc: %s\nline: %d\ndate: %s\n", file, func, line, date);
}
}
/* === 寫入函數族 === */
int WriteCharToFile(FILE *fp, char ch) {
return fputc(ch, fp) != EOF ? 0 : -1;
}
int WriteStrToFile(FILE *fp, const char *str) {
return fputs(str, fp) != EOF ? 0 : -1;
}
int WriteBlockToFile(FILE *fp, const void *block, int size, int count) {
return fwrite(block, size, count, fp) == count ? 0 : -1;
}
int WriteFormatToFile(FILE *fp) {
return fprintf(fp, "%s %s %d %s\n", __FILE__, __func__, __LINE__, __DATE__) >= 0 ? 0 : -1;
}
/* === 主程序演示 === */
int main(void) {
char ch = 'R';
char *str = "Hello World.\n";
int block[10] = {1,2,3,4,5,6,7,8,9,0};
const char *filePath = "./test_io.dat";
/* w+ 模式:文件不存在則創建,存在則清空,可讀可寫 */
fp = fopen(filePath, "w+");
if (!fp) {
perror("fopen failed");
return -1;
}
/* 執行寫入操作 */
printf("WriteStrToFile: %s\n", WriteStrToFile(fp, str) ? "Fail" : "Success");
// WriteCharToFile(fp, ch);
// WriteBlockToFile(fp, block, sizeof(block), 1);
// WriteFormatToFile(fp);
/* 關鍵步驟:rewind將文件指針重置到開頭,以便後續讀取 */
rewind(fp);
/* 執行讀取操作驗證寫入結果 */
GetLineFromFile(fp);
// GetCharFromFile(fp);
// GetBlockFromFile(fp);
// ReadFormatToFile(fp);
fclose(fp);
return 0;
}
C標準庫文件操作默認是全緩衝(塊設備)或行緩衝(終端)。fflush(fp)可強制刷新輸出緩衝區;setbuf/setvbuf可自定義緩衝策略。調試時若發現數據未寫入文件,首先檢查是否漏掉了fclose或fflush。
rewind(fp):等價於fseek(fp, 0, SEEK_SET),並清除錯誤標記fseek(fp, offset, whence):精確定位,whence可為SEEK_SET(開頭)、SEEK_CUR(當前)、SEEK_END(結尾)ftell(fp):獲取當前位置偏移量if ((fp = fopen("data.bin", "rb")) == NULL) {
perror("fopen"); // 輸出系統錯誤信息
return -1;
}
/* 操作後檢查錯誤標記 */
if (ferror(fp)) {
fprintf(stderr, "文件操作發生錯誤\n");
}
clearerr(fp); // 清除錯誤和EOF標記,繼續操作
Windows下文本模式會將\n轉換為\r\n,導致fread/fwrite處理結構體時數據長度不匹配。處理二進制數據(結構體、圖片、序列化數據)務必使用"b"模式。
while(!feof(fp))模式會多讀一次,正確寫法是直接檢查讀取函數返回值fgets需指定正確大小,fscanf的%s需限制寬度如%31sfgets+sscanf/strtok實現INI格式解析fseek實現隨機訪問,配合mmap(POSIX)提升性能tmpfile()創建自動刪除的臨時文件,mkstemp生成唯一臨時文件名flock/fcntl實現進程間文件訪問協調掌握文件操作的關鍵在於理解流的概念、緩衝機制以及錯誤狀態的傳遞。建議在實際項目中封裝一層錯誤處理統一、支持RAII風格資源管理的文件操作工具類,大幅降低資源洩漏和邏輯錯誤風險。