目錄
- 文件的基本概念
- 文本文件和二進制文件的差異
- 文件指針
- FILE 結構體
- 文件指針的初始化和賦值
- 文件打開與關閉
- 常見操作
- 文件的打開
- 文件的關閉
- 常見問題
- 打開文件時的路徑問題
- 打開文件失敗的常見原因
- fclose 函數的重要性
- 文件讀寫操作
- 常見操作
- 字符讀寫
- 字符串讀寫
- 格式化讀寫
- 二進制讀寫
- 常見問題
- 字符讀寫的性能考慮
- 字符串讀寫的邊界問題
- 格式化讀寫的精度和錯誤處理
- 二進制讀寫的字節序問題
- 文件指針的定位
- 常見操作
- fseek函數
- ftell函數
- rewind函數
- 常見應用場景
- fseek 函數的偏移量計算
- ftell 函數的應用場景
- 文件錯誤處理
- 常見操作
- ferror函數
- feof函數
- clearerr 函數
- 自定義錯誤處理函數
文件的基本概念
文本文件和二進制文件的差異
- 存儲方式
- 文本文件:以字符編碼形式存儲,例如 ASCII 碼。每個字符(如字母、數字、標點符號)都有對應的編碼值,存儲時按編碼值的二進制形式寫入文件。例如,字符 ‘A’ 的 ASCII 碼是 65,在文件中存儲為二進制的 01000001。
- 二進制文件:直接以數據在內存中的二進制表示形式存儲。例如,一個整數 10 在內存中以 32 位二進制 00000000 00000000 00000000 00001010 存儲,在二進制文件中也是這樣的形式。
- 可讀性
- 文本文件:可以用文本編輯器直接查看和編輯,因為其內容是人類可讀的字符。
- 二進制文件:用文本編輯器打開會顯示亂碼,因為它存儲的是二進制數據,需要特定的程序來解析。
- 應用場景
- 文本文件:常用于存儲配置信息、日志文件、代碼文件等,方便人類查看和修改。
- 二進制文件:常用于存儲圖像、音頻、視頻等多媒體數據,以及程序的中間數據、數據庫文件等,能更高效地存儲和處理大量數據。
文件指針
FILE 結構體
FILE 結構體是一個復雜的數據結構,包含了文件操作所需的各種信息,不同的編譯器實現可能會有所不同,但通常包含以下幾個重要成員:
- 文件狀態信息:記錄文件的打開模式、是否出錯、是否到達文件末尾等狀態。
- 文件緩沖區指針:指向用于文件讀寫的緩沖區,以提高文件操作的效率。
- 文件當前位置指針:指示當前文件讀寫的位置。
文件指針的初始化和賦值
在使用文件指針之前,需要將其初始化為 NULL,以避免成為野指針。例如:
FILE *fp = NULL;
當使用 fopen 函數打開文件后,將返回的文件指針賦值給 fp,此時 fp 就指向了打開的文件。
文件打開與關閉
常見操作
文件的打開
使用fopen
函數來打開一個文件,其原型如下:
FILE *fopen(const char *filename, const char *mode);
filename
:要打開的文件的名稱,可以包含文件的路徑。mode
:打開文件的模式,常見的模式有:"r"
:以只讀模式打開文本文件,文件必須存在。"w"
:以寫入模式打開文本文件,如果文件不存在則創建,如果文件存在則清空文件內容。"a"
:以追加模式打開文本文件,如果文件不存在則創建,寫入的數據將追加到文件末尾。"rb"
:以只讀模式打開二進制文件,文件必須存在。"wb"
:以寫入模式打開二進制文件,如果文件不存在則創建,如果文件存在則清空文件內容。"ab"
:以追加模式打開二進制文件,如果文件不存在則創建,寫入的數據將追加到文件末尾。"r+"
:以讀寫模式打開文本文件,文件必須存在。"w+"
:以讀寫模式打開文本文件,如果文件不存在則創建,如果文件存在則清空文件內容。"a+"
:以讀寫模式打開文本文件,如果文件不存在則創建,寫入的數據將追加到文件末尾,在讀取數據前需要將文件指針移動到文件開頭。"rb+"
、"wb+"
、"ab+"
:分別對應二進制文件的讀寫模式。
- 示例代碼:
#include <stdio.h>int main() {FILE *fp;// 以寫入模式打開文件fp = fopen("test.txt", "w");if (fp == NULL) {perror("文件打開失敗");return 1;}// 文件操作...fclose(fp);return 0;
}
文件的關閉
使用fclose
函數來關閉一個已打開的文件,其原型如下:
int fclose(FILE *stream);
stream:要關閉的文件指針。
返回值:如果文件關閉成功,返回 0;否則返回 EOF(-1)。
常見問題
打開文件時的路徑問題
- 絕對路徑:完整地指定了文件在文件系統中的位置,從根目錄開始。
- 在 Windows 系統中,“C:\Users\Username\Documents\test.txt”;
- 在 Linux 系統中,“/home/username/Documents/test.txt”。
- 相對路徑:相對于當前工作目錄的路徑。例如,當前工作目錄是 /home/username,要打開 Documents 目錄下的 test.txt 文件,可以使用相對路徑 “Documents/test.txt”。
打開文件失敗的常見原因
- 文件不存在:使用 “r”、“r+”、“rb”、“rb+” 等模式打開文件時,如果文件不存在,fopen 會返回 NULL。
- 權限不足:沒有足夠的權限訪問文件或目錄,例如在 Linux 系統中,嘗試以寫入模式打開一個只讀文件。
- 文件被占用:文件正在被其他程序使用,無法同時打開。
fclose 函數的重要性
關閉文件不僅可以釋放系統資源,還可以確保文件緩沖區中的數據被正確寫入磁盤。如果不關閉文件,可能會導致數據丟失或文件損壞。例如,在程序結束時,如果沒有調用 fclose 關閉文件,緩沖區中的數據可能不會被寫入文件。
文件讀寫操作
常見操作
字符讀寫
fgetc
函數:從文件中讀取一個字符,其原型如下:
int fgetc(FILE *stream);
返回值:如果讀取成功,返回讀取的字符;如果到達文件末尾或發生錯誤,返回 EOF。
fputc
函數:向文件中寫入一個字符,其原型如下:
int fputc(int c, FILE *stream);
c:要寫入的字符。
返回值:如果寫入成功,返回寫入的字符;如果發生錯誤,返回 EOF。
- 示例代碼:
#include <stdio.h>int main() {FILE *fp;// 以寫入模式打開文件fp = fopen("test.txt", "w");if (fp == NULL) {perror("文件打開失敗");return 1;}// 寫入字符fputc('H', fp);fputc('e', fp);fputc('l', fp);fputc('l', fp);fputc('o', fp);fclose(fp);// 以只讀模式打開文件fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打開失敗");return 1;}// 讀取字符int ch;while ((ch = fgetc(fp)) != EOF) {putchar(ch);}fclose(fp);return 0;
}
字符串讀寫
fgets
函數:從文件中讀取一行字符串,其原型如下:
char *fgets(char *str, int n, FILE *stream);
str:用于存儲讀取的字符串的字符數組。
n:最多讀取的字符數(包括字符串結束符 ‘\0’)。
返回值:如果讀取成功,返回 str;如果到達文件末尾或發生錯誤,返回 NULL。
fputs
函數:向文件中寫入一個字符串,其原型如下:
int fputs(const char *str, FILE *stream);
str:要寫入的字符串。
返回值:如果寫入成功,返回非負數;如果發生錯誤,返回 EOF。
- 示例代碼:
#include <stdio.h>int main() {FILE *fp;// 以寫入模式打開文件fp = fopen("test.txt", "w");if (fp == NULL) {perror("文件打開失敗");return 1;}// 寫入字符串fputs("Hello, World!\n", fp);fclose(fp);// 以只讀模式打開文件fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打開失敗");return 1;}// 讀取字符串char buffer[100];while (fgets(buffer, sizeof(buffer), fp) != NULL) {printf("%s", buffer);}fclose(fp);return 0;
}
格式化讀寫
fscanf
函數:從文件中按照指定的格式讀取數據,其原型如下:
int fscanf(FILE *stream, const char *format, ...);
stream:要讀取的文件指針。
format:格式化字符串,指定讀取數據的格式。
返回值:成功匹配并賦值的輸入項的數量,如果到達文件末尾或發生錯誤,返回 EOF。
fprintf
函數:向文件中按照指定的格式寫入數據,其原型如下:
int fprintf(FILE *stream, const char *format, ...);
stream:要寫入的文件指針。
format:格式化字符串,指定寫入數據的格式。
返回值:成功寫入的字符數,如果發生錯誤,返回負數。
- 示例代碼:
#include <stdio.h>int main() {FILE *fp;// 以寫入模式打開文件fp = fopen("test.txt", "w");if (fp == NULL) {perror("文件打開失敗");return 1;}// 寫入格式化數據int num = 10;float f = 3.14;fprintf(fp, "整數: %d, 浮點數: %f\n", num, f);fclose(fp);// 以只讀模式打開文件fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打開失敗");return 1;}// 讀取格式化數據int read_num;float read_f;fscanf(fp, "整數: %d, 浮點數: %f", &read_num, &read_f);printf("讀取的整數: %d, 讀取的浮點數: %f\n", read_num, read_f);fclose(fp);return 0;
}
二進制讀寫
fread
函數:從文件中讀取二進制數據,其原型如下:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr:用于存儲讀取數據的內存地址。
size:每個數據項的大小(字節數)。
nmemb:要讀取的數據項的數量。
返回值:實際讀取的數據項的數量。
fwrite
函數:向文件中寫入二進制數據,其原型如下:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr:要寫入的數據的內存地址。
size:每個數據項的大小(字節數)。
nmemb:要寫入的數據項的數量。
返回值:實際寫入的數據項的數量。
- 示例代碼:
#include <stdio.h>typedef struct {int id;char name[20];
} Student;int main() {FILE *fp;// 以二進制寫入模式打開文件fp = fopen("students.dat", "wb");if (fp == NULL) {perror("文件打開失敗");return 1;}// 寫入二進制數據Student s = {1, "John"};fwrite(&s, sizeof(Student), 1, fp);fclose(fp);// 以二進制只讀模式打開文件fp = fopen("students.dat", "rb");if (fp == NULL) {perror("文件打開失敗");return 1;}// 讀取二進制數據Student read_s;fread(&read_s, sizeof(Student), 1, fp);printf("ID: %d, Name: %s\n", read_s.id, read_s.name);fclose(fp);return 0;
}
常見問題
字符讀寫的性能考慮
- 緩沖區機制:fgetc 和 fputc 函數在讀寫字符時,會使用文件緩沖區。當調用 fputc 寫入字符時,字符先被寫入緩沖區,當緩沖區滿或調用 fclose 時,緩沖區中的數據才會被寫入磁盤。同樣,調用 fgetc 讀取字符時,會先從緩沖區中讀取,如果緩沖區為空,則從磁盤讀取一批數據到緩沖區。
- 頻繁讀寫的性能問題:由于每次調用 fgetc 或 fputc 都可能涉及到緩沖區的操作,頻繁的字符讀寫會導致性能下降。在需要大量讀寫字符時,可以考慮使用 fread 和 fwrite 函數進行批量讀寫。
字符串讀寫的邊界問題
fgets
函數的截斷問題:fgets 函數最多讀取 n - 1 個字符,然后在末尾添加字符串結束符 ‘\0’。如果文件中的一行字符數超過 n - 1,則會截斷該行,剩余的字符會在下一次調用 fgets 時讀取。fputs
函數的換行問題:fputs 函數不會自動添加換行符,需要手動添加。例如:
fputs("Hello\n", fp);
格式化讀寫的精度和錯誤處理
- 格式化精度:在使用 fscanf 和 fprintf 進行格式化讀寫時,需要注意格式化字符串的精度。例如,使用 %.2f 可以控制浮點數的輸出精度為兩位小數。
- 錯誤處理:fscanf 函數在讀取數據時,如果輸入數據的格式與格式化字符串不匹配,可能會導致讀取錯誤。可以通過檢查 fscanf 的返回值來判斷是否讀取成功。
二進制讀寫的字節序問題
- 字節序:在不同的計算機系統中,整數等多字節數據的存儲方式可能不同,分為大端字節序(Big Endian)和小端字節序(Little Endian)。大端字節序將高位字節存儲在低地址,小端字節序將低位字節存儲在低地址。
- 跨平臺問題:在進行二進制文件讀寫時,如果涉及到跨平臺操作,需要注意字節序的問題。可以通過字節序轉換函數(如 htons、htonl、ntohs、ntohl)來確保數據的正確讀寫。
文件指針的定位
在文件操作過程中,有時需要移動文件指針的位置,以便在文件的不同位置進行讀寫操作。
常見操作
fseek函數
fseek
函數用于將文件指針移動到指定的位置,其原型如下:
int fseek(FILE *stream, long offset, int whence);
stream:要操作的文件指針。
offset:偏移量,以字節為單位。
whence:偏移的起始位置,有三個可選值:
SEEK_SET:從文件開頭開始偏移。
SEEK_CUR:從當前文件指針位置開始偏移。
SEEK_END:從文件末尾開始偏移。
返回值:如果成功,返回 0;否則返回非零值。
- 示例代碼:
#include <stdio.h>int main() {FILE *fp;// 以只讀模式打開文件fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打開失敗");return 1;}// 將文件指針移動到文件開頭偏移3個字節的位置fseek(fp, 3, SEEK_SET);int ch = fgetc(fp);printf("讀取的字符: %c\n", ch);fclose(fp);return 0;
}
ftell函數
ftell
函數用于獲取當前文件指針的位置,其原型如下:
long ftell(FILE *stream);
stream:要操作的文件指針。
返回值:當前文件指針相對于文件開頭的偏移量(字節數),如果發生錯誤,返回 - 1。
- 示例代碼:
#include <stdio.h>int main() {FILE *fp;// 以只讀模式打開文件fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打開失敗");return 1;}// 獲取當前文件指針的位置long pos = ftell(fp);printf("當前文件指針位置: %ld\n", pos);fclose(fp);return 0;
}
rewind函數
rewind
函數用于將文件指針移動到文件開頭,其原型如下:
void rewind(FILE *stream);
stream:要操作的文件指針。
- 示例代碼:
#include <stdio.h>int main() {FILE *fp;// 以只讀模式打開文件fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打開失敗");return 1;}// 移動文件指針到文件末尾fseek(fp, 0, SEEK_END);// 將文件指針移動到文件開頭rewind(fp);int ch = fgetc(fp);printf("讀取的字符: %c\n", ch);fclose(fp);return 0;
}
常見應用場景
fseek 函數的偏移量計算
- 正數和負數偏移:fseek 函數的偏移量 offset 可以是正數或負數。正數表示向文件末尾方向偏移,負數表示向文件開頭方向偏移。例如,fseek(fp, -10, SEEK_CUR) 表示將文件指針從當前位置向文件開頭方向移動 10 個字節。
- 偏移量的范圍:偏移量的范圍受到文件大小和系統限制的影響。在某些系統中,偏移量可能不能超過文件的最大長度。
ftell 函數的應用場景
文件大小計算:可以通過將文件指針移動到文件末尾,然后使用 ftell 函數獲取文件指針的位置,從而得到文件的大小。例如:
#include <stdio.h>int main() {FILE *fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打開失敗");return 1;}fseek(fp, 0, SEEK_END);long size = ftell(fp);printf("文件大小: %ld 字節\n", size);fclose(fp);return 0;
}
文件錯誤處理
常見操作
ferror函數
ferror
函數用于檢查文件操作是否發生錯誤,其原型如下:
int ferror(FILE *stream);
stream:要檢查的文件指針。
返回值:如果發生錯誤,返回非零值;否則返回 0。
feof函數
feof
函數用于檢查文件指針是否到達文件末尾,其原型如下:
int feof(FILE *stream);
stream:要檢查的文件指針。
返回值:如果到達文件末尾,返回非零值;否則返回 0。
- 示例代碼:
#include <stdio.h>int main() {FILE *fp;// 以只讀模式打開文件fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打開失敗");return 1;}int ch;while ((ch = fgetc(fp)) != EOF) {putchar(ch);}if (ferror(fp)) {printf("文件讀取發生錯誤\n");} else if (feof(fp)) {printf("到達文件末尾\n");}fclose(fp);return 0;
}
clearerr 函數
clearerr
函數用于清除文件的錯誤標志和文件結束標志,其原型如下:
void clearerr(FILE *stream);
stream:要操作的文件指針。
在發生錯誤后,可以使用 clearerr 函數清除錯誤標志,以便繼續進行文件操作。例如:
#include <stdio.h>int main() {FILE *fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打開失敗");return 1;}int ch;while ((ch = fgetc(fp)) != EOF) {if (ferror(fp)) {printf("文件讀取發生錯誤\n");clearerr(fp); // 清除錯誤標志}putchar(ch);}fclose(fp);return 0;
}
自定義錯誤處理函數
可以編寫自定義的錯誤處理函數,以便在文件操作發生錯誤時進行統一處理。例如:
#include <stdio.h>void handle_file_error(FILE *fp, const char *message) {if (ferror(fp)) {perror(message);clearerr(fp);}
}int main() {FILE *fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打開失敗");return 1;}int ch;while ((ch = fgetc(fp)) != EOF) {handle_file_error(fp, "文件讀取錯誤");putchar(ch);}fclose(fp);return 0;
}