C 語言文件流詳解
解讀文件流
C 語言對文件的寫入和讀取涉及到流的概念,寫入爲輸出流,讀取爲輸入流。如何理解流的概念呢?可以把流看成流動的自來水,打開水龍頭,自來水就會通過自來水管從水源流到用戶家中,同樣的道理,水庫中的水也會通過管道流入到水源。從水源流出到用戶住家爲自來水的輸出流,從水庫流入到水源爲自來水的輸入流,只有這樣,自來水才能源源不斷地輸送到用戶家中。
如果把水源看成文件,用戶住家爲讀取文件的函數,水庫爲寫入文件的函數,就很容易理解 C 語言的輸入與輸出流了。當 C 語言的寫入函數(水庫)需要將數據寫入到文件(水源)時,需要建立一條從寫入函數(水庫)到文件(水源)的通道,這個通道就是輸入流;當 C 語言的讀取函數(用戶住家)需要讀取文件(水源)時,也需要建立一條從文件(水源)到讀取函數(用戶住家)的通道,這個通道就是輸出流。
C 語言的讀寫函數在讀寫文件時,並不會直接把數據寫入到文件,或直接讀取到程序接收文件數據的變量,它會建立一個文件緩衝區用來存放讀寫的數據。當進行文件讀取時,讀取函數先打開數據流,將磁盤上的文件信息拷貝到緩衝區內,然後程序再從緩衝區中讀取所需數據。當寫入函數將數據寫入文件時,並不會馬上寫入磁盤中,而是先寫入緩衝區,只有在緩衝區已滿或 “關閉文件” 時,纔會將數據寫入磁盤文件。
在 C 語言中,文件流是通過 FILE 結構體來表示的。FILE 結構體在 C 標準庫中定義,包含了文件操作的所有信息,如文件指針、緩衝區等。程序通過文件指針來訪問和操作文件流。
**將文本數據寫入文件 **
文本數據寫入文本文件的流程:調用 fopen() 函數打開或創建一個新的文件,調用 fputc() 函數或 fputs() 函數進行單字符寫入或文本行寫入,最後調用 fclose() 函數關閉文件。
函數聲明
int fputc ( int character, FILE * stream );
函數將字符 character 寫入到 stream 指向的文件緩衝區。若寫入成功返回寫入的字符,否則返回 EOF。
函數聲明
int fputs ( const char * str, FILE * stream );
函數將 str 字符串寫入到 stream 指向的文件緩衝區,字符串的結束符不會寫入到文件緩衝區。若寫入成功返回非負值,否則返回 EOF。
函數聲明
int fprintf ( FILE * stream, const char * format, ... );
函數將格式化字符串 format 寫入到 stream 指向的文件緩衝區,如果 format 包含格式說明符(以 % 開頭的子序列),則格式化 format 後面的附加參數,並將其插入結果字符串中,替換其各自的說明符。若寫入成功,返回寫入的字符數,否則返回負值。
下面的示例程序展示瞭如何將文本數據寫入文件。
#include
int main() {
// 定義文件名
const char *filename = "example.txt";
// 文本數據
const char *text = "Hello, World! This is an example text.";
// 打開文件,準備寫入。如果文件不存在,則創建它。
// "w" 模式表示寫入模式,會覆蓋文件中的任何現有內容。
FILE *file = fopen(filename, "w");
if (file == NULL) {
// 如果文件無法打開或創建,打印錯誤消息並退出程序
perror("Error opening file");
return 1;
}
fputs(text, file);
// 關閉文件
fclose(file);
// 打印成功消息
printf("Text written to file successfully.\n");
return 0;
}
上述程序使用 fputs() 函數將指針變量 text 指向的文本數據寫入 example.txt 文件,程序會在當前目錄下創建一個名爲 example.txt 的文件(如果該文件已經存在,其內容將被覆蓋)。
注意:程序在寫入文件時,需要有寫入文件的權限,並且文件路徑是有效的。如果文件打開或寫入失敗,fopen 函數將返回 NULL,程序將打印一個錯誤消息並退出。
**讀取文本文件內容到緩衝區 **
C 語言提供了多個 C 函數用於讀取文本文件,其中最常用的是 fscanf、fgets 和 fread。
fscanf 函數
fscanf 函數用於從文件中讀取格式化輸入。函數原型:
int fscanf(FILE *stream, const char *format, ...);
stream 通過 fopen 函數返回的文件指針。
format 控制字符串,指定了讀取數據的格式。
... 表示格式字符串中指定的額外參數。
例如,如果有一個包含整數的文本文件,可以使用 fscanf 來讀取這些整數:
int number;
FILE *file = fopen("numbers.txt", "r");
if (file != NULL) {
while (fscanf(file,"%d",&number) != EOF) {
printf("%d\n", number);
}
fclose(file);
}
上述例子打開一個名爲 "example.txt" 的文本文件,並使用 fscanf 從文件中讀取一個十進制整數,將其存儲到 number 變量中。如果讀取成功,打印出讀取到的整數;如果讀取失敗,打印一個錯誤消息。
**fgets 函數 **
fgets 函數用於從文件中讀取一行文本。函數原型:
char *fgets(char *str, int n, FILE *stream);
str 是一個字符數組,用於存儲讀取的文本行。
n 是要讀取的最大字符數(包括空字符)。
stream 通過 fopen 函數返回的文件指針。
例如,可以使用 fgets 來逐行讀取文本文件的內容:
char line[100];
FILE *file = fopen("example.txt", "r");
if (file != NULL) {
while (fgets(line, sizeof(line), file) != NULL) {
printf("%s", line);
}
fclose(file);
}
上述例子打開一個名爲 "example.txt" 的文本文件,並使用 fgets 函數 從文件中逐行讀取文本內容,將其存儲到字符數組 line 內。如果讀取成功,打印出讀取到的內容。
**文件隨機訪問 **
在 C 語言中,fseek 和 ftell 函數用於文件隨機訪問,它們可以操作文件指針,實現對文件任意位置的讀寫操作。rewind 函數將文件內部的位置指針重新指向開始。
**fseek 函數 **
fseek 是 C 語言中的一個標準庫函數,用於移動文件的位置指針到指定的位置,函數定義在頭文件中。fseek 允許程序在讀取或寫入文件時,不按照順序從頭到尾或從尾到頭進行,而是可以直接跳轉到文件的任意位置進行操作。
函數原型:
int fseek(FILE *stream, long int offset, int whence);
參數:
stream:指向 FILE 結構體的指針標識一個打開的文件流。
offset:表示偏移量的長整數。它表示從 whence 指定的位置開始移動的字節數。
whence:決定了 offset 的起始位置。它有三個可能的值:
SEEK_SET:從文件開始位置計算偏移量。
SEEK_CUR:從當前文件位置計算偏移量。
SEEK_END:從文件末尾計算偏移量。
返回值:
如果函數執行成功,fseek 返回零。如果發生錯誤,它會返回非零值。
**ftell 函數 **
C 語言中的 ftell 函數用於確定文件流中的當前讀寫位置,函數定義在頭文件中,主要用於處理文件操作。
函數原型:
long ftell(FILE *stream);
ftell 函數接受一個指向 FILE 結構體的指針,函數返回一個 long 類型的值,表示從文件開頭到當前讀寫指針位置的字節偏移量。如果發生錯誤,函數將返回 - 1。
ftell 函數配合 fseek 函數可以獲取文件的大小;在讀取或寫入大文件時,可以通過 ftell 函數來監控文件的讀寫進度;fseek 函數用於設置文件流中的讀寫位置,ftell 可以用於驗證 fseek 是否設置成功。
**rewind 函數 **
C 語言中的 rewind 函數將文件流中的位置指針指向流的開始,函數定義在頭文件中。
函數原型:
void rewind(FILE *stream);
rewind 函數作用等同於 fseek(stream, 0L, SEEK_SET)。
**讀寫二進制文件 **
C 語言提供了 fread 函數從文件讀取二進制數據,fwrite 函數將二進制數據寫入到文件。
**fread 函數 **
該函數從 stream 輸入流讀取 count 個元素,每個元素的大小爲 size 個字節,並存儲到 ptr 指向的內存中。若讀取成功,返回讀取的元素總個數,若返回小於 count 的數值,則表示在讀取時發生讀取錯誤或到達文件末尾,在這種情況下,可以使用可以分別用 ferror 和 feof 進行檢查。
函數原型
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
size_t 是標準 C 庫中使用 typedef 關鍵字定義的基礎數據類型的別名,在 64 位系統中爲 long long unsigned int,非 64 位系統中爲 long unsigned int。
函數原型
int ferror ( FILE * stream );
函數檢查是否發生了與 stream 關聯的錯誤,如果有關聯錯誤則返回不同於零的錯誤值,否則返回零值。
函數原型
int feof ( FILE * stream );
函數檢查是否發生了與 stream 關聯的文件結束指示符,如果設置了,則返回一個不同於零的值,否則返回零值。
下面的示例程序演示瞭如何使用 fread 函數讀取一個圖片文件(例如,一個 PNG 文件),並將其內容存儲到一個緩衝區中。
#include
#include
int main() {
FILE *file;
char *filename = "example.png"; // 圖片文件名
long filesize; // 文件大小
void *buffer; // 用於存儲文件內容的緩衝區
// 打開文件
file = fopen(filename, "rb");
if (file == NULL) {
perror("Error opening file");
return EXIT_FAILURE;
}
// 移動到文件末尾以獲取文件大小
fseek(file, 0, SEEK_END);
filesize = ftell(file);
// 重置文件指針到文件開頭
rewind(file);
// 分配足夠的內存來存儲文件內容
buffer = malloc(filesize);
if (buffer == NULL) {
perror("Memory allocation failed");
fclose(file);
return EXIT_FAILURE;
}
// 使用fread讀取文件內容到緩衝區
if (fread(buffer, 1, filesize, file) != filesize) {
perror("Error reading file");
free(buffer);
fclose(file);
return EXIT_FAILURE;
}
// 讀取完成,關閉文件
fclose(file);
// 釋放buffer佔用的內存
free(buffer);
return EXIT_SUCCESS;
}
在上述示例中使用 fopen 函數以二進制模式("rb")打開圖片文件,然後使用 fseek 和 ftell 函數來確定文件的大小,接着分配一個足夠大的緩衝區來存儲整個文件的內容,並使用 fread 函數將其讀取到緩衝區中,最後關閉文件並釋放緩衝區的內存。
**fwrite 函數 **
fwrite 函數用於向 stream 指向的文件流,寫入 size*count 字節數據,參數 ptr 指向待寫入的二進制數據。
函數原型
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
參數:
ptr:指向某內存空間的指針,該內存空間中儲存有待寫入文件的數據塊;參數 ptr 類型爲 void * 型,說明 ptr 可以指向任何數據類型;
size:指定了每個待寫入文件的數據項的字節大小,類型爲 size_t(unsigned int);
count:指定了待寫入文件的數據項的個數,類型爲 size_t(unsigned int)型;
stream:指向 FILE 類型結構的指針。
返回值:
fwrite 函數返回一個 size_t 類型的值,表示實際寫入的數據元素數量。如果返回值小於 count,則表示發生了錯誤。
下面的示例程序將一個整數數組寫入到一個二進制文件中。
#include
int main() {
FILE *fp;
int data[] = {1, 2, 3, 4, 5};
int count = sizeof(data) / sizeof(data[0]);
fp = fopen("data.bin", "wb"); // 以二進制寫模式打開文件
if (fp == NULL) {
perror("Error opening file");
return 1;
}
size_t elements_written = fwrite(data, sizeof(int), count, fp);
if (elements_written < count) {
perror("Error writing to file");
fclose(fp);
return 1;
}
fclose(fp);
return 0;
}
fwrite 函數寫入的是二進制數據,不是文本數據。因此,在讀取這些數據時,應該使用 fread 函數,並且注意數據的類型和對齊方式。
如果寫入的數據類型與讀取時的數據類型不匹配,或者數據的對齊方式不正確,可能會導致讀取的數據與原始數據不一致。
fwrite 函數不會檢查文件流是否已經打開或是否已經到達文件末尾,所以在調用 fwrite 之前,最好先檢查文件流是否有效。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/BEeKihMxwiB3VE5rLSgezQ