前言

程序中数据的输入可以从键盘读取,但是对于大量的数据,人工输入的效率过于低下,而且对于多次运行的工作极为不便。所以可以通过程序对文件进行读取,然后将结果保存到另一个文件中,这样可以大幅度提高效率。

文件概述

文件的定义

文件指存储在外部存储介质中的有序数据集合,如系统头文件stdio.h,程序所生成的源文件.c文件,编译后产生的.obj目标文件,链接后生成的.exe执行文件。

文件的分类

  1. 从用户的角度来看,文件可以分为普通文件和设备文件

    普通文件是驻留在存储介质上的有序数据集。。普通文件依据其存储内容可以分为程序文件,如源文件,头文件等,以及数据文件,存储待输入的原始数据和输出的结果数据文件。

    C语言将所有外部设备都看作文件,这就是设备文件,如显示器,打印机,键盘等,将它们对系统的输入,输出等同于对磁盘文件的读和写。通常将显示器作为标准输出文件,在屏幕上的显示即是向标准输出文件输出,printf()putchar()称为标准输出函数即原因。键盘则作为标准输入文件,从键盘上输入即标准输入文件读入数据,因此scanf()getchar()称为标准输入函数。

  2. 虽然文件在计算中皆是用二进制 0,1 来表达与存储,但从文件的编码方式来看,文件可以分为 ASCII码文件和二进制码文件。一般文件的最基本存储单位为字节(8位二进制),文件即是由一个个字节按一定顺序构成的,但每个字节表达含义不同,则文件编码方式也不同。

    ASCII码文件:也称为文本文件。在磁盘中存放的文件的每个字节都是对应字符的ASCII码。例如,对数值5678存储为对应字符5,6,7,8的ASCII码,形式为:

    image-20220218143616511

    ASCII码文件可在屏幕上按字符显示,因此方便阅读,如源程序文件,头文件便ASCII码文件。

    二进制文件:文件在磁盘中存放的是对应数值的二进制形式。如数值5678存储的二进制表示为 0001011000101110 。二进制文件的优点在于节省存储空间,但是可读性较差。

    C语言在处理文件时,并不区分类型,都按字节处理,看成字符流。输入/输出字符流的开始和结束也只是由程序控制而不受物理符号(如回车符)的控制。因此也将这种文件称为流式文件或流文件,这是文件较为重要的一个概念。

文件类型指针

在C语言中,对文件的所有操作都是通过文件类型指针来进行的。文件类型指针是指向文件结构体变量的指针。所谓文件结构体变量是指文件处理时,在缓存区开辟的文件信息描述区,而该信息描述区是以一个结构体变量来描述和记录文件的当前状态(如文件名,文件大小等)。描述和记录文件状态的结构体变量称为文件结构体变量,其结构体类型由系统定义,名为FILE,包含在头文件stdio.h,因此文件操作必须使用#include <stdio.h>命令。C语言便是通过操作指向文件结构体变量的指针来进行文件处理,有时也简称为指向文件的指针或者文件指针。

FILE结构体类型的形式大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct{
short _level; //缓冲区满空程度
unsigned int _flag; //文件号
char _fd; //文件描述符
short _size; //缓存区的大小
char *_buffer; //数据缓冲区首地址
int _cleft; //缓冲区中剩下的字符
int _mode; //文件的操作模式
char *_curp; //指针当前位置
char *_nextc; //下个字符的位置
unsigned int _istemp; //临时文件指示
short _token; //有效性标记
};

不同C语言系统FILE类型的定义会有少许不同。文件指针的定义如下:

1
FILE * 指针变量标识符;

一般习惯写成:

1
FILE * fp;

通过指针fp指向某个具体文件来对文件进行操作。

fp是:file Pointer 的缩写

文件的打开,读写和关闭

在进行文件的读写之前需要先打开文件,读写完毕之后必须关闭文件,打开与关闭文件是必不可少的操作。打开文件即是建立文件指针与文件的关系,关闭则是释放指针与文件的联系,同时保证缓冲区中的数据写入文件。

文件的打开函数fopen()

fopen()函数原型如下:

1
FILE * fopen(const char * filename,const char * mode);

利用fopen()函数打开文件方式:

1
2
3
4
5
FILE * fp;
fp=fopen("文件名","文件操作方式标识符");

//代码示例
fp=fopen("myfile","w"); //以只写的方式打开文件myfile

调用函数时,系统会在缓冲区为文件开辟一个文件信息描述区,获得该文件信息描述区(文件结构变量)的地址,并将它赋值给指针fp,从而fp与文件联系起来,通过fp便可以实现对文件的各种操作。如果文件不能打开(打开文件失败),则fopen()函数返回空指针NULL(其值为0).

文件名可以包含文件路径,例如:

1
fp=fopne("C:\\test\nyfile","w");

上述打开文件方式,为ANSIC规定方式。

American National Standards Institute(ANSI——美国国家标准学会)

文件操作方式标识符如下标:

文件操作方式标识符 意义
“r” 以只读的方式打开一个文本文件,只允许读数据
“w” 以只写的方式打开或建立一个文本文件,只允许写数据
“a” 以追加方式打开一个文本文件,并在文件末尾增加数据
“rb” 以只读方式打开一个二进制文件,只允许读数据
“wb” 以只写方式打开或建立一个二进制文件,只允许写数据
“ab” 以追加方式打开一个二进制文件,并在文件末尾写数据
“r+” 以读写方式打开一个文本文件,允许读和写
“w+” 以读写方式打开或者建立一个文本文件,允许读和写
“a+” 以读写方式打开一个文本文件,允许读,或者在文件末尾追加数据
“rb+” 以读写方式打开一个二进制文件,允许读和写
“wb+” 以读写方式打开或者建立一个二进制文件,允许读和写
“ab+” 以读写方式打开一个二进制文件,允许读,或在文件末尾追加数据

需要注意的是:

  1. 以只读方式r打开文件时,该文件必须以及存在,否则出错,且只能进行读取操作。

  2. 以只写方式w打开文件时,如果文件不存在,则以指定的文件名新建文件,若打开的文件已经存在,则原文件内容消失,重写写入内容且只能进行写操作。

  3. 以追加方式a打开文件,若文件不存在则出错,若文件存在则向文件末尾追加新的信息

  4. 如一个文件无法打开,或者打开出错,将无法进行正确读写操作,如果不对文件打开加以判断,则用户无法了解是否可以进行下一步操作,因此文件操作除打开,关闭这两个要素外,还需要打开判断。**如果打开出错,fopen()函数将返回一个空指针NULL**,因此在程序中可以使用下面的语句进行判断是否打开:

    1
    2
    3
    4
    5
    if((fp=fopen("myfile","w"))==NULL){
    printf("error: fail in opening myfile");
    getch();
    exit(1);
    }

    getch()函数是获取字符,但是不显示在屏幕上。

    exit(1)是系统级别的函数,表示异常退出,exit(0)表示正常退出。

  5. 文本文件读入内存时,需要将ASCII码转换成二进制码,写入磁盘时,再把二进制码转换成ASCII码,因此文本文件的读写相比于二进制文件,需要花费转换时间。

文件的关闭函数fclose()

文件打开成功并操作完毕后,如不关闭文件,文件读写的数据可能丢失。因为文件的操作是通过缓冲区进行的,读写数据是先放入缓冲区,满时才写入文件,如操作后缓冲区未满,又未关闭文件,则缓冲区中的数据将丢失,因此必须使用文件关闭命令,将缓冲区的数据写入文件。文件关闭函数fclose()原型为:

1
2
3
4
5
//语法格式
int fclose(FILE *fpoint);

//代码示例
fclose(fp);

如果关闭成功,则fclose()函数返回 0 ,否则返回 EOF(-1)。通过判断fclose()函数返回值可知晓文件是否正常关闭。

1
2
if((rNum=fclose(fp)!=0))
printf("error:fail in file close");

文件关闭不仅可以保存数据,同时还会释放文件结构体变量所占存储空间,可节省系统资源。

【关于EOF的说明】

以下内容来源于百度百科 EOF

在C语言中,或更精确地说成C标准函数库中表示文件结束符(end of file)。在while循环中以EOF作为文件结束标志,这种以EOF作为文件结束标志的文件,必须是文本文件。在文本文件中,数据都是以字符ASCII代码值的形式存放。我们知道,ASCII代码值的范围是0~127,不可能出现-1,因此可以用EOF作为文件结束标志。

文件的读写

除了打开,打开判断,关闭这三个文件操作要素外,对文件实际的改变操作是中间对文件的读和写。C语言提高多种文件读写函数,也包含在头文件stdio,h中,主要有:

  • 文件字符读/写函数:fgetc()/fputc()
  • 文件字符串读/写函数:fgets()/fputs()
  • 文件格式化读/写函数:fprintf()/fscanf()
  • 文件数据块读/写函数:fread()/fwrite()

所有读函数,都必须是读或读写方式打开文件;所有写函数,都必须是写或者读写的方式,或者追加方式打开文件。如果希望重建文件,则采用只写或者读写的方式打开文件,如果希望保留原文件内容,从后面开始新内容,则用追加或者读写方式打开文件。

  1. 文件字符读写函数

    • **文件读字符函数fgetc()**,函数原型:

      1
      int fgetc(FILE * fpoint);

      fgetc()函数调用形式如下:

      1
      c=fgetc(fp);

      其中c为字符变量,也可以是数组字符元素等。**fgetc()函数每次从文件中读取一个字符,返回值为该字符的 ASCII 码,如返回值为 EOF ,则表示已到达文件结束位置。**

      打开文件后,fgetc()函数读取的是第一个字符,再调用fgetc()函数则依次读取下一关字符,如果读至结束则返回 EOF 。实际上,读写位置是由文件内部的位置指针控制的,打开时,位置指针指向第一个字节,并随着函数的调用后移,该位置指针由系统自动设置,不需要用户定义。

    • **文件写字符函数fputc()**,函数原型:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      //函数原型
      int fputc(int c,FILE * fpoint);

      //语法格式
      fputc(字符量,文件fp);

      //代码示例
      char c='a';
      fputc(c,fp);

      fputc()函数每次向文件写入一个字符,写入成功则返回字符的 ASCII 码值,写入失败则返回 EOF

    【实例】显示.txt文件内容并写入新内容,再显示新内容

    【代码示例】

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    #include <stdio.h>
    void PutOut(FILE *fp); //输出文件内容函数原型
    void WriteNew(FILE *fp); //输入新内容函数原型
    int main(){
    FILE *fp; //声明文件指针
    //文件指针指向文件并设置打开失败输出
    //a+ 允许读和追加
    if((fp=fopen("txt.txt","a+"))==NULL){
    printf("error: fail in opening myfile");
    }
    PutOut(fp); //输出原内容
    WriteNew(fp); //输入新内容
    PutOut(fp); //输出新内容
    fclose(fp); //关闭文件指针
    return 0;
    }
    //输出原文件的内容
    void PutOut(FILE * fp){
    printf("文件的内容:\n");
    rewind(fp); //将文件指针移动到开始处
    char c = fgetc(fp);
    while (c>0)
    {
    printf("%c", c);
    c = fgetc(fp);
    }
    }
    //添加新内容
    void WriteNew(FILE * fp){
    printf("\n请输入新增内容:\n");
    char x = getchar();
    while (x!='\n')
    {
    fputc(x, fp);
    x = getchar();
    }
    printf("输入完成\n");
    }

    【输出】

    image-20220220164146907
  2. 文件字符串读写函数

    • **文件字符串读函数fgets()**,函数原型:

      1
      2
      3
      4
      char * fgets(char *s,int n,FILE * fpoint);

      //调用形式
      fgets(字符数组名,n,文件指针);

      其功能是从文件指针所指文件中读取一个长度为n-1的字符串,在最后一个字符之后加上字符串结束标志\0后,存入一个字符数组中。

      **若fgets()函数执行成功,则返回字符数组首元素的地址,如果一开始就遇到文件尾或者数据错误,则返回NULL**。

    • **文件字符串写函数fputs()**,函数原型:

      1
      2
      3
      4
      5
      6
      7
      int fputs(char * string,FILE * fpoint);

      //调用形式
      fputs(字符串,文件指针);

      //代码示例
      fputs("Hello",fp);

      其功能是往文件中写入一个字符串,其中字符串可以是字符串常量,也可以是有赋值的字符数组。如果写入成功,则返回一个非负值,反之则返回 E0F (-1)。

    【实例】读取文件前10个字符输出显示,然后从键盘输入字符串写入文件并显示

    【代码示例】

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    #include <stdio.h>
    void CloseFile(FILE *fp); //关闭文件函数
    void OpenFile(FILE *fp); //打开文件函数
    void ReadFile(FILE *fp,int num); //读取文件函数
    void WriteFile(FILE *fp); //写入文件函数
    int main(){
    FILE *fp=fopen("txt.txt","a+");
    OpenFile(fp);
    ReadFile(fp,12);
    WriteFile(fp);
    ReadFile(fp,20);
    CloseFile(fp);
    return 0;
    }
    //打开文件函数
    void OpenFile(FILE * fp){
    if(fp==NULL){
    printf("error: fail in opening myfile");
    }
    }
    //文件关闭函数
    void CloseFile(FILE * fp){
    fclose(fp);
    }
    //读取文件函数
    void ReadFile(FILE * fp,int num){
    printf("\n读取文件内容: ");
    rewind(fp);
    char c[20];
    fgets(c, num, fp);
    printf("%s", c);
    printf("\n文件内容读取完毕\n");
    }
    //写入文件函数
    void WriteFile(FILE * fp){
    printf("\n请输入要添加的内容: ");
    fseek(fp, 8, SEEK_END); //将指针移到文件最后
    char x[10];
    scanf("%s", x);
    if (fputs(x, fp)<0)
    {
    printf("ERROR");
    }
    printf("内容接收完毕\n");
    }

    【输出】

    image-20220220185220626

    关于库函数fseek()的说明或者查看下面的文件定义的说明

  3. 文件格式化读写函数

    • **文件格式化读函数fscanf()**。

      fscanf()函数与scanf()函数功能类似,区别在于scanf()函数从标志输入文件(即键盘)读取,fscanf()函数则是从文件中读取。函数原型:

      1
      2
      3
      4
      5
      6
      7
      int fscanf(FILE * fpoint,char * format,[argument...]);

      //调用形式
      fscanf(文件指针,格式控制字符串,输入项列表);

      //代码示例
      fscanf(fp,"%c%d",&c,&a);
    • **文件格式化写函数fprintf()**。

      fprintf()函数与printf()函数功能类似,区别在于pritnf()函数输出到标志输出文件(即显示器),fprintf()函数则输出到磁盘文件。函数原型:

      1
      2
      3
      4
      5
      6
      7
      int fprintf(FILE * fpoint,char * format,[argument...]);

      //调用形式
      fprintf(文件指针,格式控制字符串,输出项列表);

      //代码示例
      fprintf(fp,"%c%d",c,a);

    【实例】读取指定数量字符,并写入文件其 ASCII 码值

    【代码示例】

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    #include <stdio.h>
    void OpenFile(FILE *fp); //打开文件函数原型
    void CloseFile(FILE *fp); //关闭文件函数原型
    void OpeFile(FILE *fp); //操作文件函数原型
    int main(){
    FILE *fp = fopen("txt.txt", "a+");
    OpenFile(fp);
    OpeFile(fp);
    CloseFile(fp);
    return 0;
    }
    //打开文件函数
    void OpenFile(FILE * fp){
    if(fp==NULL){
    printf("error: fail in opening myfile");
    }
    }
    //文件关闭函数
    void CloseFile(FILE * fp){
    fclose(fp);
    }
    //读取文件函数
    void OpeFile(FILE * fp){
    printf("\n读取文件内容: ");
    rewind(fp);
    char a, b;
    fscanf(fp, "%c%c", &a, &b);
    printf("%c%c(前两个字符)", a, b);
    printf("\n文件内容读取完毕\n");
    printf("正在写入文件");
    fseek(fp, 8, SEEK_END); //将文件指针移到最后
    fprintf(fp, "%d%d", a, b);
    printf("写入文件完成\n");
    }

    【输出】

    image-20220220193029544

    需要注意的是每次读取文件的时候,注意将文件指针使用rewind()函数移动到开始

  4. 文件数据块读写函数

    所谓数据块读写是指一次读取一组数据,如数组,结构体变量等。数据块读写函数调用形式如下:

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

    其中buffer为输入或者输出数据首地址,为指针变量,size为数据块长度(字节数),count表示要读写的数据块的个数,fp为文件指针。代码示例:

    1
    2
    3
    //代码示例
    char str[20];
    fread(str,3,5,fp);

    即从fp所指的文件中每次读取 3 个字节,读取 5 次,存入数组str中。

    fread()以及fwrite()函数的返回值都是整型,如果该整数和count相等,则表示读写是成功的,否则表示读写不正确。

    【实例】某超市把以下4种库存商品的信息写入 commodity.dat 文件中,并读出来进行检测。

    商品编号 商品名称 商品价格 商品库存量
    1001 电视 4500 10
    1002 空调 8000 15
    1003 冰箱 5000 6
    1004 洗衣机 6000 30

    【代码示例】

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #include <stdio.h>
    typedef struct Commodity
    {
    int id;
    char name[10];
    int price;
    int num;
    }Commodity;
    int main(){
    //初始化数据
    Commodity commd[4] = {{1001,"电视",4500,10}, {1002,"空调",4500,10}, {1001,"冰箱",4500,10}, {1001,"洗衣机",4500,10}};
    Commodity commd2[4];
    FILE *fp = fopen("commodity.dat","ab+"); //打开文件指针
    fwrite(commd, sizeof(Commodity), 4, fp); //将缓冲区数据写入文件
    rewind(fp); //指针指向文件开头
    fread(commd2, sizeof(Commodity), 4, fp); //将数据写入文件缓冲区
    printf("文件中的内容为:\n");
    for (int i = 0; i < 4; i++)
    {
    printf("%d | %s | %d | %d \n", commd2[i].id,commd2[i].name,commd2[i].num,commd2[i].price);
    }
    fclose(fp);
    return 0;
    }

    【输出】

    image-20220220200605907

文件的定位

前面的实例中多次使用了rewind()函数,用来将文件位置指针返回到文件开头。所以,我们在操作文件时,应该先清楚文件位置指针在什么位置,根据位置来进行处理。文件定位函数便是操作文件位置指针用于判断以及其指定其位置的函数

文件定位函数包含在头文件stdio.h

  1. rewind()函数

    1
    2
    3
    4
    5
    //函数原型
    void rewind(FILE * fpoint);

    //调用形式
    rewind(fp);

    不论当前指针位置在哪里,rewind()函数都将文件位置指针返回文件开头。

  2. fseek()函数

    1
    2
    3
    4
    5
    //函数原型
    int fseek(FILE * fpoint,long offset,int origin);

    //调用形式
    fseek(fp,位移量,起始点);

    fseek()函数用于把文件位置指针移动到指定位置上。起始点有3个取值:

    • 0(SEEK_SET)表示文件开始位置;
    • 1(SEEK_CUR)表示当前位置;
    • 2(SEEK_END)表示文件末尾;

    位移量表示从起始点开始移动的字节数,为长整型。位移量为正表示文件指针向文件末尾移动,反之则向文件开头方向移动。代码示例:

    1
    fseek(fp,8,SEEK_END);

    表示移动文件位置指针到文件的末尾,共移动 8 个字节。

  3. ftell()函数

    1
    2
    3
    4
    5
    //函数原型
    loog ftell(FILE * fpoint);

    //调用形式
    loog n = ftell(fp);

    **ftell()函数用于寻找位置指针的当前位置。返回值为位置指针当前位置相对于文件首的偏移字节数。如果调用出错,则返回-1L**。

  4. feof()函数

    feof()函数用于判断文件位置指针是否在文件结束位置。

    1
    2
    3
    4
    5
    //函数原型
    int feof(FILE * stream);

    //调用形式
    feof(fp);

    feof()函数返回值为 1 表示位置指针在文件末尾,否则返回 0 。