| 類別 | 函數 | 用途 |
|---|---|---|
| 打開/關閉 | fopen | 打開文件,返回 FILE* |
| fclose | 關閉文件,刷新緩衝區 | |
| fflush | 強制刷新輸出緩衝區 | |
| 字符讀寫 | fgetc / getc | 讀單字符(函數/宏) |
| fputc / putc | 寫單字符 | |
| getchar / putchar | 標準輸入/輸出單字符 | |
| ungetc | 回退字符到輸入流 | |
| 行/塊讀寫 | fgets | 讀一行(含換行符) |
| fputs | 寫字符串(不含結束符) | |
| fread / fwrite | 二進制塊讀寫(結構體/數組) | |
| 定位/狀態 | fseek / ftell | 移動/獲取文件指針位置 |
| rewind | 重置到文件開頭 | |
| feof / ferror / clearerr | 檢查 EOF/錯誤/清除標誌 | |
| 格式化 I/O | fprintf / fscanf | 格式化讀寫 |
| sprintf / sscanf | 字符串格式化(內存操作) |
FILE *fopen(const char *path, const char *mode);
模式字符串:
r:只讀,文件必須存在w:只寫,截斷或創建a:追加寫,創建或末尾+:讀寫雙向(r+, w+, a+)b:二進制模式(Windows 區分換行符,Linux 忽略)返回:成功返回 FILE*,失敗返回 NULL 並設置 errno。
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
適合結構體、數組整體讀寫。返回實際完成的項數(nmemb),size*nmemb 為總位元組數。
int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);
whence:SEEK_SET(開頭)、SEEK_CUR(當前)、SEEK_END(結尾)。ftell 返回當前偏移量,-1 表示錯誤。
int feof(FILE *stream); // 非零=已到 EOF
int ferror(FILE *stream); // 非零=發生錯誤
void clearerr(FILE *stream); // 清除標誌
關鍵模式:讀取循環以 fread/fgets 返回值為判斷條件,不要 用 feof() 控制循環(會多讀一次)。
/**********************************************************************
* 文件操作綜合演示
* 編譯: gcc -Wall -o file_demo file_demo.c
**********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define BUFSZ 256
int main() {
FILE *fp;
char buf[BUFSZ];
long pos;
size_t cnt;
/* 1. 寫入文本文件 */
fp = fopen("demo.txt", "w");
if (!fp) { perror("fopen w"); return 1; }
fprintf(fp, "第一行: Hello World\n");
fprintf(fp, "第二行: 數字 %d %f\n", 42, 3.14);
fputs("第三行: 直接寫入字符串\n", fp);
fclose(fp);
/* 2. 追加二進制數據 */
struct { int id; double val; } records[3] = {
{1, 1.1}, {2, 2.2}, {3, 3.3}
};
fp = fopen("demo.bin", "wb");
if (!fp) { perror("fopen wb"); return 1; }
cnt = fwrite(records, sizeof(records[0]), 3, fp);
printf("寫入 %zu 條記錄\n", cnt);
fclose(fp);
/* 3. 文本讀取 - 行讀取 */
fp = fopen("demo.txt", "r");
if (!fp) { perror("fopen r"); return 1; }
printf("=== 文本內容 ===\n");
while (fgets(buf, BUFSZ, fp)) {
printf("%s", buf); // fgets 保留 \n
}
fclose(fp);
/* 4. 二進制讀取 - 隨機訪問 */
fp = fopen("demo.bin", "rb");
if (!fp) { perror("fopen rb"); return 1; }
// 移動到第 2 條記錄
fseek(fp, 1 * sizeof(records[0]), SEEK_SET);
struct { int id; double val; } rec;
cnt = fread(&rec, sizeof(rec), 1, fp);
if (cnt == 1) {
printf("第 2 條記錄: id=%d, val=%.1f\n", rec.id, rec.val);
}
// 獲取文件大小
fseek(fp, 0, SEEK_END);
long fsize = ftell(fp);
printf("文件大小: %ld 位元組\n", fsize);
fclose(fp);
/* 5. 錯誤處理演示 */
fp = fopen("not_exist.txt", "r");
if (!fp) {
printf("預期錯誤: %s (errno=%d)\n", strerror(errno), errno);
}
return 0;
}
| 錯誤模式 | 後果 | 正確做法 |
|---|---|---|
| 未檢查 fopen 返回值 | 空指針解引用崩潰 | 必檢查,失敗打印 strerror(errno) |
| feof 控制讀取循環 | 多處理一次 EOF 數據 | 以 fread/fgets 返回值為循環條件 |
| 文本模式讀二進制 | Windows 換行符轉換損壞數據 | 二進制文件用 "rb"/"wb" |
| 忘記 fclose | 緩衝區未刷新、文件描述符洩漏 | 配對 fclose,異常路徑也要關閉 |
| 緩衝區溢出 | gets/fscanf 不限長度 | 用 fgets/snprintf,指定尺寸 |
| ftell 用於文本流 | Windows 下位置不準 | 僅二進制流用 ftell/fseek |
fseeko/ftello (off_t)、fopen64、編譯加 -D_FILE_OFFSET_BITS=64flockfile/funlockfile 鎖定 FILE*,或用 fread_unlocked 等無鎖版本配合外部鎖mmap() 大文件隨機訪問性能更優,屬 POSIX 而非標準 Ctmpfile() 自動刪除、mkstemp() 安全創建