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