C語言詳解FILE文件操作

1. 需要了解的概念

需要理解的知識點包括:數據流、緩衝區、文件類型、文件存取方式
 

1.1 數據流:

指程序與數據的交互是以流的形式進行的。進行C語言文件的存取時,都會先進行“打開文件”操作,這個操作就是在打開數據流,而“關閉文件”操作就是關閉數據流。

1.2 緩衝區(Buffer):

指在程序執行時,所提供的額外內存,可用來暫時存放做準備執行的數據。它的設置是爲了提高存取效率,因爲內存的存取速度比磁盤驅動器快得多。

 C語言中帶緩衝區的文件處理:

C語言的文件處理功能依據系統是否設置“緩衝區”分爲兩種:一種是設置緩衝區,另一種是不設置緩衝區。由於不設置緩衝區的文件處理方式,必須使用較低級的I/O函數(包含在頭文件io.h和fcntl.h中)來直接對磁盤存取,這種方式的存取速度慢,並且由於不是C的標準函數,跨平臺操作時容易出問題。下面只介紹第一種處理方式,即設置緩衝區的文件處理方式:

當使用標準I/O函數(包含在頭文件stdio.h中)時,系統會自動設置緩衝區,並通過數據流來讀寫文件。當進行文件讀取時,不會直接對磁盤進行讀取,而是先打開數據流,將磁盤上的文件信息拷貝到緩衝區內,然後程序再從緩衝區中讀取所需數據,如下圖所示:

事實上,當寫入文件時,並不會馬上寫入磁盤中,而是先寫入緩衝區,只有在緩衝區已滿或“關閉文件”時,纔會將數據寫入磁盤,如下圖所示。


1.3 文件類型:

分爲文本文件和二進制文件兩種。

文本文件是以字符編碼的方式進行保存的。二進制文件將內存中數據原封不至文件中,適用於非字符爲主的數據。如果以記事本打開,只會看到一堆亂碼。

其實,除了文本文件外,所有的數據都可以算是二進制文件。二進制文件的優點在於存取速度快,佔用空間小,以及可隨機存取數據。
 

1.4 文件存取方式:

包括順序存取方式和隨機存取方式兩種。

順序讀取也就是從上往下,一筆一筆讀取文件的內容。保存數據時,將數據附加在文件的末尾。這種存取方式常用於文本文件,而被存取的文件則稱爲順序文件。

隨機存取方式多半以二進制文件爲主。它會以一個完整的單位來進行數據的讀取和寫入,通常以結構爲單位。


2. 文本文件操作

C語言中主要通過標準I/O函數來對文本文件進行處理。相關的操作包括打開、讀寫、關閉與設置緩衝區。
相關的存取函數有:fopen(), fclose(), fgetc(), fputc(), fgets(), fputs(), fprintf(), fscanf()等。
 

2.1 打開文件

函數原型爲:_CRTIMP FILE * __cdecl fopen(const char *, const char *);

第一參數爲文件名,第二個參數爲打開模式。

打開成功,fopen返回一個結構指針地址,否則返回一個NULL。如果沒有指定文件路徑,則默認爲當前工作目錄。如:

FILE *fp;
fp = fopen("c:\\temp\\test.txt", "r") //由於反斜槓\是控制字符,所以必須再加一個反斜槓

 

使用fopen()函數打開的文件會先將文件複製到緩衝區。注意:所下達的讀取或寫入動作,都是針對緩衝區進行存取而不是磁盤,只有當使用fclose()函數關閉文件時,緩衝區中的數據纔會寫入磁盤。
 

  談文件打開模式

打開文本文件:

"r":只能從文件中讀數據,該文件必須先存在,否則打開失敗
"w":只能向文件寫數據,若指定的文件不存在則創建它,如果存在則先刪除它再重建一個新文件
"a":向文件增加新數據(不刪除原有數據),若文件不存在則打開失敗,打開時位置指針移到文件末尾
"r+":可讀/寫數據,該文件必須先存在,否則打開失敗
"w+":可讀/寫數據,用該模式打開新建一個文件,先向該文件寫數據,然後可讀取該文件中的數據
"a+":可讀/寫數據,原來的文件不被刪去,位置指針移到文件末尾

打開二進制文件的模式與打開文本文件的含義是一樣的,不同的是模式名稱裏面多一個字母'b’,以表示以二進制形式打開文件。
 

2.2 關閉文件

函數原型爲:_CRTIMP int __cdecl fclose(FILE *);

關閉成功返回值0,否則返回非零值。

注:在執行完文件的操作後,要進行“關閉文件”操作。雖然程序在結束前會自動關閉所有的打開文件,但文件打開過多會導致系統運行緩慢,這時就要自行手動關閉不再使用的文件,來提高系統整體的執行效率。

 

例1. 打開文件並進行判斷和關閉文件

複製代碼

FILE *fp;
fp = fopen("c:\\temp\\test.txt", "r");

if(fp == NULL)
    printf("fail to open the file! \n");
else
{
    printf("The file is open! \n");
    fclose(fp);
}

複製代碼

 

2.3 字符存取函數

函數原型爲:

_CRTIMP int __cdecl fputc(int, FILE *);
_CRTIMP int __cdecl fgetc(FILE *);

字符讀取函數fgetc()可從文件數據流中一次讀取一個字符,然後讀取光標移動到下一個字符,並逐步將文件的內容讀出。

如果字符讀取成功,則返回所讀取的字符,否則返回EOF(end of file)。EOF是表示數據結尾的常量,真值爲-1。另外,要判斷文件是否讀取完畢,可利用feof()進行檢查。未完返回0,已完返回非零值。

feof()函數原型爲:_CRTIMP int __cdecl feof(FILE *);

例2. fgetc()函數的使用

版本1:利用feof()函數檢查文件是否讀取完畢

複製代碼

#include <stdio.h>

main()
{
    FILE *fp;
    fp = fopen("c:\\temp\\test.txt", "r");
    if(fp != NULL)
    {
        while(!feof(fp))
            printf("%c", fgetc(fp));
    }
    else
        printf("fail to open! \n");
    fclose(fp);

    return 0;
}

複製代碼


版本2:利用文件結束標誌EOF(即-1)

複製代碼

#include <stdio.h>

main()
{
    char ch;
    FILE *fp;
    fp = fopen("c:\\temp\\test.txt", "r");
    if(fp != NULL)
    {
        ch = fgetc(fp);
        while(ch != EOF)
        {
            putchar(ch);
            ch = fgetc(fp);
        }

    }
    else
        printf("fail to open! \n");
    fclose(fp);

    return 0;
}

複製代碼


版本3 - 重構版本2

複製代碼

#include <stdio.h>

main()
{
    char ch;
    FILE *fp;
    if((fp = fopen("test.txt", "r")) != NULL)
        while((ch = fgetc(fp)) != EOF)
            putchar(ch);
    else
        printf("fail to open! \n");
    fclose(fp);

    return 0;
}

複製代碼


版本4 - 重構版本3 (不正確的重構)

複製代碼

#include <stdio.h>

main()
{
    FILE *fp;
    if((fp = fopen("test.txt", "r")) != NULL)
        while(fgetc(fp) != EOF)
            putchar(fgetc(fp));
    else
        printf("fail to open! \n");
    fclose(fp);

    return 0;
}

複製代碼



若要將字符逐一寫入文件,用fputc()函數。示例爲:

例3. fputc()函數的使用

複製代碼

#include <stdio.h>
#include <conio.h>

main()
{
    char filename[20], ch;
    FILE *fp;
    printf("Enter a filename: ");
    scanf("%s", filename);
    printf("Enter some characters to output to file: ");
    if((fp = fopen(filename, "w")) == NULL)
        printf("fail to open! \n");
    else
    {
        while((ch = getche()) != '\015')
            fputc(ch, fp);
    }
    fclose(fp);

    return 0;
}

複製代碼


 

2.4 字符串存取函數

函數原型爲:

_CRTIMP int __cdecl fputs(const char *, FILE *);
_CRTIMP char * __cdecl fgets(char *, int, FILE *);

fgets函數的作用是從指定文件讀入一個字符串,如:fgets(str, n, fp);

參數n爲要求得到的字符個數,但只從fp指向的文件輸入n-1個字符,然後在最後加一個'\0'字符,因此得到的字符串共有n個字符,把它們放在字符數組str中。如果在讀完n-1個字符之前遇到換行符或EOF,讀入結束。

fputs函數的作用是向指定文件輸出一個字符串,如:fputs("Hey", fp);

把字符串"Hey"輸出到fp指向的文件。fputs函數的第一個參數可以是字符串常量、字符數組名或字符型指針。若輸出成功,則返回0,否則返回EOF。

實例略

 

2.5 格式化存取函數

函數原型爲:

_CRTIMP int __cdecl fprintf(FILE *, const char *, ...);
_CRTIMP int __cdecl fscanf(FILE *, const char *, ...);

它們與printf和scanf函數相仿,都是格式化讀寫函數。不同的是:fprintf和fscanf函數的讀寫對象不是終端(標準輸入輸出),而是磁盤文件。printf函數是將內容輸出到終端(屏幕),因此,fprintf就是將內容輸出到磁盤文件了。

實例4. fprintf和fscanf函數的使用

複製代碼

#include <stdio.h>

void main()
{
    FILE *fp;

    int num = 10;
    char name[10] = "Leeming";
    char gender = 'M';

    if((fp = fopen("info.txt", "w+")) == NULL)
        printf("can't open the file! \n");
    else
        fprintf(fp, "%d, %s, %c", num, name, gender); //將數據格式化輸出到文件info.txt中

    fscanf(fp, "%d, %s, %c", &num, name, &gender); //從文件info.txt中格式化讀取數據
    printf("%d, %s, %c \n", num, name, gender); //格式化輸出到屏幕

    fclose(fp);
}

複製代碼


 

2.6 指針重返函數

函數原型爲:

_CRTIMP void __cdecl rewind(FILE *);;

rewind函數的作用是使位置指針重返回文件的開頭,屬於文件的定位。

 

3. 二進制文件操作

3.1 數據塊存取函數

函數原型:

_CRTIMP size_t __cdecl fread(void *, size_t, size_t, FILE *);
_CRTIMP size_t __cdecl fwrite(const void *, size_t, size_t, FILE *);

當要求一次存取一組數據(如,一個數組、一個結構體變量的值),fread和fwrite函數可以解決該類問題。它們的調用形式一般爲:

fread(buffer, size, count, fp);
fwrite(buffer, size, count, fp);

buffer:對於fread來說,指的是讀入數據的存放地址;對於fwrite來說,是要輸出數據的地址。
size:讀寫數據時,每筆數據的大小
count:讀寫數據的筆數
fp:文件指針
 

實例5. fread和fwrite函數的使用

複製代碼

#include <stdio.h>
#define SIZE 3

typedef enum { MM, GG } Gender;

typedef struct
{
    char name[10];
    int  age;
    Gender gender;
} Person;

void write2file(Person emp[SIZE])
{
    FILE *fp;
    if((fp = fopen("emp.txt", "wb")) == NULL)
    {
        printf("cannot open file! \n");
        return;
    }

    for(int i=0; i<SIZE; i++)
        if(fwrite(&emp[i], sizeof(Person), 1, fp) != 1)
            printf("file write error! \n");
    fclose(fp);
}

void read_from_file(FILE *fp)
{
    Person emp_out[SIZE];

    if((fp = fopen("emp.txt", "rb")) == NULL)
    {
        printf("cannot open file! \n");
        return;
    }

    printf("\n%d employee's information read: \n", SIZE);

    for(int i=0; i<SIZE; i++)
    {
        if(fread(&emp_out[i], sizeof(Person), 1, fp) != 1)
            if(feof(fp))
            {
                fclose(fp);
                return;
            }
            printf("%-5s %4d %5d \n", emp_out[i].name, emp_out[i].age, emp_out[i].gender);
    }
    fclose(fp);
}

void main()
{
    FILE *fp = NULL;
    Person employee[SIZE];

    printf("Enter %d employee's information: \n", SIZE);
    for(int i=0; i<SIZE; i++)
        scanf("%s %d %d", employee[i].name, &employee[i].age, &employee[i].gender);

    write2file(employee);

    read_from_file(fp);
}

複製代碼


 

3.2 隨機存取函數fseek()

函數原型:

_CRTIMP int __cdecl fseek(FILE *, long, int);

對流式文件可以進行順序讀寫,也可以進行隨機讀寫。關鍵在於控制文件的位置指針,如果位置指針是按字節位置順序移動的,就是順序讀寫。如果能將位置指針按需要移動到任意位置,就可以實現隨機讀寫。所謂隨機讀寫,是指讀完上一個字符(字節)後,並不一定要讀寫其後續的字符(字節),而可以讀寫文件中任意位置上所需要的字符(字節)。該函數的調用形式爲:

fseek(fp, offset, start);

start:起始點。用0、1、2代替。0代表文件開始,名字爲SEEK_SET,1代表當前位置,名字爲SEEK_CUR,2代表文件末尾,名字爲SEEK_END。

fseek()函數一般用於二進制文件,因爲文本文件要發生字符轉換,計算位置時往往會發生混亂。

調用實例如:

fseek(fp, i*sizeof(Person), 0);

 

https://blog.csdn.net/yong_sun/article/details/8921061

發佈了25 篇原創文章 · 獲贊 7 · 訪問量 1998
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章