C语言文件操作

本章主要介绍最后C语言的文件读写操作

文件概述

C语言中的文件是指计算机中保存数据的一种方式,它可以用于存储程序代码、配置文件、文本文件和二进制数据等。在C语言中,使用文件操作相关的库函数来进行文件的读写操作,包括打开、关闭、读取、写入等操作。

在C语言中,文件是用文件指针(File Pointer)来表示的。文件指针是一个指向文件数据的指针,它指向文件的起始位置或当前位置。使用文件指针可以对文件进行读写操作,包括使用fopen()函数打开文件,使用fclose()函数关闭文件,使用fread()函数读取文件内容,使用fwrite()函数写入文件内容等。另外,也可以使用fseek()函数来控制文件指针的位置。

在C语言中,文件一般分为文本文件和二进制文件两种类型。文本文件中存储的是以ASCII码表示的文本字符,而二进制文件中存储的是二进制数据。在读取和写入文件时,需要使用不同的函数或参数来区分是文本文件还是二进制文件。

 


  • 举例:一个简单的例子输出helloworld到file f1.txt
#include<stdio.h>
#include<stdlib.h>

int main(){
    FILE *fp; //定义文件指针
    fp=fopen("f1.txt","w");
    if(fp== NULL)
    {printf("打开文件失败\n");
    exit(0);}
    fprintf(fp,"Hello World!");
     if(fclose(fp)){printf("关闭文件失败\n");
    exit(0);}
   

    return 0;
 }

在这里插入图片描述
没定义文件地址时候默认在cpp文件目录下

 


文件的概念

文件:保存在外存储器上的一组数据的有序集合

特点:

  1. 数据长久保存
  2. 数据长度不变
  3. 数据按顺序存取

 


逻辑文件

C语言把外部设备也作为文件对待 (这样的文件称为设备文件) ,从而把实际的物理设备抽象化成为逻辑文件的概念

每个设备文件也都有一个文件名,比如打印机作为设备文件时,系统命名为PRN。

C语言把磁盘文件和设备文件都作为相同的逻辑文件对待,对它们的输入和输出采用相同的方法进行。这种逻辑上的统一为程序设计提供了很大的方便

 


文件的分类

  • 根据文件的组织形式,可分为顺序存取文件和随机存取文件
  • 根据文件的存储形式,可分为ASCII文件(文本文件)和二进制文件

文本文件和二进制文件

  • ASCII码 (文本文件 text stream) 字符流
  • 二进制码(二进制文件 binary stream)
    二进制文件是直接把内存数据以二进制形式保存

例如,整数1234

  • 文本文件保存: 49 50 51 52 (4个字符) (ASCII码49是1)
  • 二进制文件保存: 04D2 (1234的二进制数)

C 语言将文件看作是由一个一个的字符(ASCII码文件) 或字节(二进制文件) 组成的。将这种文件称为流式文件

 


缓冲文件系统

所谓缓冲文件系统是指,系统自动地在内存区为每个正在使用的文件开辟一个缓冲区

  • 向磁盘输出数据: 数据 -> 缓冲区 ,装满缓冲区后 -> 磁盘文件
  • 从磁盘读入数据: 先一次性从磁盘文件讲一批数据输入到缓冲区,然后再从缓冲区逐个读入数据到变量。

在这里插入图片描述

  • 缓冲文件系统(Buffered File System)是一种文件系统,它采用缓冲区(Buffer)作为数据的中间存储区域,来提高文件系统的访问效率和数据传输速度。缓冲文件系统可以有效地将磁盘I/O次数降到最低,从而提高文件系统的性能。

  • 缓冲文件系统通过将文件的数据读取到缓存中,而不是每次直接读取磁盘上的数据,极大地减少了磁盘I/O的使用。缓冲文件系统会在文件读取或写入时,将数据先存储在内存中的缓存区中。如果需要进行写入,则先存储在缓存区中,当缓存区满时,再将缓存区的数据写入到磁盘中。如果需要进行读取,则先从缓存区中读取数据,如果缓存区中没有,则再从磁盘中读取数据。

  • 缓冲文件系统的主要优点是能够减少访问磁盘次数,提高文件的访问效率;同时,缓冲文件系统还可以增加数据的安全性,即使在系统崩溃时也能保持数据的完整性。
    但是,由于缓存区的大小有限,如果文件过大,将会降低缓冲文件系统的效率。此外,缓冲文件系统的实现需要占用一定的内存资源,也需要更复杂的算法来管理缓存区,从而增加了系统的开销。


 

文件结构 FILE

系统给每个打开的文件都在内存中开辟个区域,用于存放文件的有关信息 (如文件名、文件位置等)。

这些信息保存在个结构类型变量中,该结构类型由系统定义、取名为FILE。

! 注意: 结构类型名“FILE”必须大写。

FILE 结构定义

  • FILE:结构类型
  • stdio.h定义 用typedef定义
typedef struct {
    short           level;         // 缓冲区使用量
    unsigned        flags;         // 文件状态标志
    char            fd;            // 文件描述符
    short           bsize;         // 缓冲区大小
    unsigned char   *buffer;       // 文件缓冲区的首地址
    unsigned char   *curp;         // 指向文件缓冲区的指针
    unsigned        hold;          // 其他信息
    unsigned        HFILE;         
    unsigned        istemp;        // 是否为临时文件
    short           token;      
} FILE;

文件类型指针

有了FILE类型后,可以使用文件类型指针指向多个文件的信息

  • FILF *fp;
    指向文件缓冲区,通过移动指针实现对文件的操作

就是定义一个文件指针fp,指向该文件对应的结构变量,即可通过文件指针访问该文件。

  • 举例: fp=fopen(“f1.txt”,“w”);
    就是给fp赋值,fp指向f1.txt文件。有了文件指针后,对文件的操作都通过它进行。

在这里插入图片描述

 


文件的打开和关闭

文件打开函数fopen

fopen定义

一般用来打开文件,其调用的形式

  • 文件指针名=fopen(文件名,使用文件方式);

其中

  • 文件指针名 必须是FILE类型的变量
  • 文件名 是指要打开(或创建) 的文件名。如果使用字符数组(或字符指针)则不使用双引号
  • 文件打开方式 包括rw等

举例:

FILE *fp; //定义文件指针
    fp=fopen("d:\\buded\\f1.txt","w");
    if(fp== NULL)
    {printf("打开文件失败\n");
    exit(0);}

\表示转义字符\,这样就是文件的目录层次

fopen的返回值

  • 执行成功,则返回包含文件缓冲区等信息的FILE型地址,赋给文件指针fp
  • 不成功,则返回一个空指针NULL(其值在头文件stdio.h中被定义为 0)

❗️fopen的打开方式列表

文本文件(ASCII)二进制文件(Binary)
r打开一个用于读取的文件。该文件必须存在rb打开只读文件
w创建一个用于写入的空文件。如果文件名称与已存在的文件相同,则会删除已有文件的内容,文件被视为一个新的空文件wb建立只写新文件
a追加到一个文件。写操作向文件末尾追加数据。如果文件不存在,则创建文件ab打开添加写文件
r+打开一个用于更新的文件,可读取也可写入。该文件必须存在rb+打开读/写文件
w+创建一个用于读写的空文件wb+建立新读/写新文件
a+打开一个用于读取和追加的文件ab+打开读/写文件

总结: r是只读,w是新文件写入,a是在文件后面追加。加上+就是可读取也可以写入
——t表示文本文件,b表示二进制文件

下面是一些注意事项:

  1. 如果以“读”方式打开文件,指定文件必须存在,否则出错;
  2. 如果以“写”方式打开文件,则指定的文件可以存在,也可以不存在
  3. 如果 以"w"方式写,如果 该文件已经存在,则原文件将被删去重新建立; 否则,按指定的名字新建一个文件;
  4. 如果 以"a"方式写,如果 该文件已经存在,写入的数据将被添加到指定文件原有数据的后面,不会删去原来的内容:否则,按指定的名字新建一个文件(与“w”相同)
  5. 如果 文件同时读和写,使用"r+"、"w+”或"a+"打开文件

 


文件关闭函数fclose

一般用来打开文件,其调用的形式:

  • 文件关闭函数=fclose(文件指针);

fclose函数关闭文件指针所指的,也就是使文件指针变量与文件”脱钩“。

举例:

if(fclose(fp)){printf("关闭文件失败\n");
    exit(0);}
    //因为fclose关闭成功返回0,失败返回非0,所以这样如果返回失败就会触发下面的
  • 关闭文件成功,返回0
  • 关闭文件失败,返回EOF(-1)

 


输入/输出重定向函数freopen

三种标准文件

在程序运行中,系统会自动打开三种标准文件(文件流形式)

  • 标准输入(stdin):通常是键盘用于从用户输入中读取数据

在 Linux 和其他类 Unix 操作系统中,可以使用重定向来替代标准输入。例如,我们可以使用 ./a.out < input.txt 命令将文件 “input.txt” 中的数据重定向到标准输入流。操作系统会将该文件视为标准输入流,从而将其中的数据读取到程序中。

  • 标准输出(stdout):通常是控制台屏幕用于向用户输出信息

在 Linux 和其他类 Unix 操作系统中,也可以使用重定向将标准输出重定向到文件中。例如,我们可以使用 ./a.out > output.txt 命令将程序输出的信息重定向到文件 “output.txt” 中。

  • 标准错误输出(stderr)用于向用户输出错误信息

标准输出和标准错误输出通常是分开的,这样一来,当程序发生错误时,可以将错误信息写入到标准错误输出流中,而不会干扰到标准输出流中输出的信息
在类 Unix 操作系统中,也可以使用重定向将标准错误输出重定向到文件中。例如,我们可以使用 ./a.out 2> error.txt 命令将程序输出的错误信息重定向到文件 “error.txt” 中,这样一来,我们就可以方便地查看程序的错误信息。

三种标准文件存在的问题

使用标准输入输出设备文件存在的问题主要有以下几点:

  1. 从键盘输入数据时,用户输入的数据只能用一次。
  2. 输出到屏幕的信息只能看不能编辑。

为了解决这些问题,才提出freopen重定向。主要用于改变stdio\stdout\stderr关联的文件

 


freopen定义(输入输出重定向)

它可以用于将标准的输入/输出重定向到指定的文件上,或者重新打开一个文件作为指定文件的输入/输出流。

也就是说可以不用输入可以不用键盘,而来自一个指定的文件。

  • 定义:FILE *freopen(const char *filename, const char *mode, FILE *stream);

其中

  • filename 参数表示文件名;
  • mode 参数表示文件操作模式,可以是 “r”、“w”、“a”、“rb”、“wb”、“ab” 和 “r+”、“w+”、“a+”、“rb+”、“wb+”、“ab+” 等;
  • stream 参数表示需要重新打开文件的文件指针。(标准文件)

举例:

  • 输入重定向
freopen("in.txt","r",stdin);
//把标准输出流stdin重定向到in.txt中
//这样在用scanf输入时候,就不会从键盘读取数据,而是从in.txt中获取
  • 输出重定向
freopen("out.txt","w",stderr); 
//把可执行程序的标准输出或标准错误输出重定向到out.txt中写入。
//这样用printf输出时候,就不会显示到屏幕上,而是保存到out.txt中

 

freopen举例

下面看个例子:

举例:计算a+b,从in.txt中读取数据,结果保存在out.txt中

//计算a+b,从in.txt中读取数据,结果保存在out.txt中
#include<stdio.h>

int main(void)
{
    int a,b;
    freopen("in.txt","r",stdin); //输入重定向,r模式,in.txt必须存在
    freopen("out.txt","w",stdout); //输出重定向
    while (scanf("%d%d",&a,&b)!=EOF)
    {
        printf("%d\n",a+b);
    }
    fclose(stdin);//stdin、stderr、stdout都是标准文件
    fclose(stdout);
    return 0;
}

首先这里没有路径,所以要在工程目录下面新建一个文件in.txt

运行结果:
在这里插入图片描述
在这里插入图片描述

 


文件的读/写操作

字符读写函数fgetc()和fputc()

这是单个字符的读写操作的函数,可以处理文件

字符写函数fputc()

fputc()函数的功能是把一个字符写到磁盘文件上,同时移动读写位置指针到下一个写入位置。

  • 定义:fputc(字符型变量,文件指针)

函数返回值:

  • 成功写入,返回字符型变量。
  • 写入失败,返回EOF(-1)。

字符读函数fgetc()

fgetc的作用是从磁盘文件读取一个字符

  • 定义: fgetc(文件指针)

函数返回值:

  • 成功读取,返回文件指针所指示磁盘文件读出的字符。同时读写位置指针向后移动1个字节(1个字符)
  • 读取失败,返回EOF(-1)。

 

字符读写函数举例

举例:将键盘上输入的一个字符串(以@作为结束字符),以ASCII码形式存储到一个磁盘文件f2.txt中,再顺序显示出这个磁盘文本文件。

解题思路:这个程序对f2.txt分别进行读写操作。从键盘输入字符写入f2.txt时,文件按写方式打开,将f2.txt的内容显示到屏幕时,文件按读方式打开。在这里,读和写是二种不同的操作,f2.txt分别被打开和关闭二次

代码:

#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *fp;
    char ch;

    // 打开文件
    fp = fopen("f2.txt", "w");
    if(fp == NULL) {
        printf("文件打开失败!\n");
        exit(0);
    }

    // 读取键盘输入,并将字符写入文件
    printf("请输入字符串,输入@结束:");
  for ( ; (ch = getchar())!='@';)
  {
     fputc(ch, fp); // 将字符写入文件
  }

    // 关闭文件
    fclose(fp);


    // 重新打开文件,并读取文件内容并输出到屏幕上
    fp = fopen("f2.txt", "r");
    printf("\n文件内容为:\n");
    for ( ; (ch = fgetc(fp))!=EOF;)
  {
     putchar(ch); // 将读取的字符输出到屏幕上
  }
  printf("\n");

    // 关闭文件
    fclose(fp);
    return 0;
}

运行结果:

在这里插入图片描述

 


上面是单个字符的读写,下面介绍下字符串的读写操作

字符串读写函数fputs()和fgets()

字符串写函数fputs()

用于向指定的文本文件写入一个字符串

  • 定义:fputs(字符数组,文件指针);

字符数组可以是字符串常量,字符指针变量名。需要注意的是结束符’\0’不写入文件。

函数返回值:

  • 成功写入,返回非负数。
  • 写入失败,返回EOF。

字符读函数fgets()

从指定的文本文件中读取字符串

  • 定义:fgets(字符数组, 字符个数, 文件指针);
  • fgets(str, n, fp);

函数返回值:

  • 成功写入,返回读取的字符串。
  • 写入失败,返回空指针,字符串内容不确定。

其中:

  • 字符数组str:可以是字符数组名或字符指针;
  • n: 指定读入的字符个数;
  • fp:文件指针

1. 从指定文件中读入一个字符串,存入str中,并在尾端自动加一个’\0’,同时将读写位置指针向前移动strlength(字符串长度)个字节(以便于重新开始读取)。

2. 函数被调用时,最多读取n-1个字符

3. 如果在读入规定长度之前遇到文件结束标志EOF或换行符,读入即结束若有换行符,则将换行符保留(换行符在’\0’符之前) ,若有EOF,则不保留

 


字符串读写函数举例

将键盘上输入的一个长度不超过80的字符串,以ASCII码形式存储到一个磁盘文件f3.txt中;然后再输出到屏幕上。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    FILE *fp;
    char str[81]; //字符数组用于暂存输入输出字符串

    // 打开文件
    fp = fopen("f3.txt", "w");
    if(fp == NULL) {
        printf("文件打开失败!\n");
        exit(0);
    }

    // 读取键盘输入,在文件中存储输入的字符串
    printf("请输入一个长度不超过80的字符串:");
    //gets(str);
    fgets(str, 81, stdin); //从键盘输入字符串
    fputs(str, fp); //存储到fp指向的文件
    
    // 关闭文件
    fclose(fp);

    // 重新打开文件,并读取文件内容并输出到屏幕上
    fp = fopen("f3.txt", "r");
    printf("\n文件内容为:\n");
    fgets(str,strlen(str)+1,fp); //从fp文件读一个字符串,+1是为了'\0'结束符号

    puts(str);//显示到屏幕上
    printf("\n");

    // 关闭文件
    fclose(fp);
    return 0;
}

在这里插入图片描述

 


格式化文件读/写函数fscanf和fprintf

格式化读写函数

一般调用方式

fscanf(文件指针,格式字符串,输入表);
fprintf(文件指针,格式字符串,输出表);

例如,下面表示从文件a.txt中分别读入整形数据到变量a,x

FILE *fp;
int n; float x;
fp=fopen("a.txt","r");
fscanf(fp,"%d%f",n,x);

把上面变量n,x的数值写入文件b.txt

fp=fopen("b.txt","w");
fprintf(fp,"%d%f",n,x);

下面举个例子吧:
从dat1.dat整形数据里面读出20个数据存入数组中,并写入到dat2.dat中,同时将写入的数据输出到屏幕上

//从dat1.dat整形数据里面读出20个数据存入数组中,并写入到dat2.dat中,同时将写入的数据输出到屏幕上

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    FILE* fp;
    FILE* fp1;
    

     fp= fopen("dat1.dat", "r"); 
    if (fp == NULL) {
        printf("文件打开失败!\n");
        exit(0);
    }
      fp1= fopen("dat2.dat", "w"); 
    if (fp1 == NULL) {
        printf("文件写入失败!\n");
        exit(0);
    }

    //定义数组
    int i,count=0;
    int array[20];

    // 从dat1文件中读取前20个整数
    for ( i = 0; i < 20; i++)
    {
        fscanf(fp,"%d",array+i);
    }
    // 输出读取到的整数
    printf("读取的整数如下:\n");
    for (i = 0; i < 20; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");
    //将20个数据写入dat2
    for ( i = 0; i < 20; i++)
    {
        fprintf(fp1,"%d ",*(array+i));
    }
    fclose(fp);
    fclose(fp1);
    return 0;
}

运行结果:
在这里插入图片描述
在这里插入图片描述

 

数据块读\写函数fread()和fwrite() (二进制文件)

实际应用中,常常要求成组的数据写入到文件中或从文件中读到内存中。 为此,ANSII 标准设置了 fread( )和fwrite0函数。

可以实现对数据的整体读写。通常用于二进制文件,被读写的数据可以是一个结构体或数组,也可以是一个简单数据,总之要求文件中每一组数据的存储长度相同。

  • 定义格式:
    fread(数据区首地址,每次读取的字节数,读取次数,文件指针);
    fwrite(数据区首地址,每次写入的字节数,写入次数,文件指针);

  • 函数调用返回值
    成功返回数据个数,如果实际读写的次数小于指定次数,按实际的次数来
    失败返回0

! 注意

  • fread()从文件指针所指向文件的当前位置开始次读入size个字节,重复count次,并将读入的数据存放到从首地址开始的内存中;
    同时,将读写位置指针向前移动size*count个字节。其中buffer是存放读入数据的起始地址(即存放何处)。

  • fwrite()从buffer开始,一次输出size个字节,重复count次,并将输出的数据存放到fp所指向的文件中;
    同时,将读写位置指针向前移动size*count个字节。

 

举例结构体读取写入:

若有如下定义:

struct student
{
 char name[10];
 int score;
}stu, stuArr[10];

若将结构体变量stu写入文件,语句如下

fwrite(&stu,sizeof(stu),1,fp);

若将结构体数组stuArr写入文件,语句如下:

fread(stuArr,sizeof(stuArr),1,fp);

若将结构数组stuArr的前n个元素写入文件,语句如下:

fwrite(stuArr,sizeof(stuArr[0]),n,fp);

解释一下,这里的长度大小是一次写入多少个,这里sizeof(stuArr[0])是一个结构体大小,所以后面重复n次

fwrite 函数的
第一个参数是要写入的数据的指针,即 stuArr 数组的起始地址;
第二个参数是要写入的元素大小,stuArr 中的一个元素大小是 sizeof(stuArr[0]),即结构体的大小;
第三个参数是要写入的元素个数,这里写入的是数组中的前 n 个元素,因此指定 n。

举例:

#include <stdio.h>
#include <stdlib.h>

// 定义结构体类型
struct student
{
    char name[10];
    int score;
} stu;

int main()
{
     int n = 5; // 读取前5个结构体数组元素
     FILE* fp = fopen("student.dat", "wb+"); // 以二进制方式打开文件
    FILE* fp1 = fopen("studentout.dat", "wb"); // 以二进制方式打开文件

    if (fp == NULL) {
        printf("文件打开失败!\n");
        return -1;
    }

  struct student stuArr[10] = {
        { "Tom", 80 },
        { "Jerry", 90 },
        { "Alice", 75 },
        { "Bob", 85 },
        { "David", 92 }
    };
    fwrite(stuArr,sizeof(stuArr[0]),n,fp);

    // 读取结构体数组
   fread(stuArr,sizeof(stuArr[0]),n,fp);
    // 输出读取到的结构体数组
    for (int i = 0; i < n; i++)
    {
        printf("name: %s, score: %d\n", stuArr[i].name, stuArr[i].score);
    }
    fwrite(stuArr,sizeof(stuArr[0]),n,fp1);
    fclose(fp);
    fclose(fp1);
    return 0;
}

运行结果:
在这里插入图片描述
在这里插入图片描述
因为是二进制文件打开的。vscode要安装Hex Editor
在这里插入图片描述
 


文件的其他操作

文件定位函数

顺序读写文件和随机读写文件

前面讲的都是顺序读\写一个文件,即打开一个文件后都有一个读写指针来标识读写位置,根据打开模式指向文件的起始处或结尾处。

在顺序读写文件时,我们需要按照指定的打开模式打开文件,然后使用文件指针函数(如 fread() 和 fwrite())读写文件。读写操作时,文件指针会根据读写数据的大小自动向后移动,以便下一次读写操作能够定位到正确的位置。在读取完文件中所有的数据后,文件指针会指向文件的末尾。

值得注意的是,在使用 fread() 和 fwrite() 函数进行文件读写时,我们需要确保每次读写的字节数与文件中的数据类型大小相同,否则可能会读写出现错误。此外,还要注意检查文件是否成功打开,以及读写操作是否成功完成,并释放资源。

除了顺序读写,还允许对文件进行随机读写,可以通过定义函数来改变读写指针的位置。

位置指针定位函数fseek

定义格式

fseek函数可以将位置指针移到指定的位置。

  • 定义:fseek(文件指针,偏移量,起始点);

其中:

  • offset:移动偏移量,long型(以起点为基准,向文件尾或文件头移动的字节数,如果是正数,表示向文件尾部正向移动,如果是负数,表示向文件头部反向移动

  • from:起始位置,文件首部、当前位置和文件尾部分别对应0,1,2,或常量SEEK_SET、SEEK_CUR、SEEK_END

返回值

  • 成功返回0

  • 失败返回非0值

  • 举例:

fseek(fp,20L,0); //将文件位置指针移到文件首的20字节处
fseek(fp,-20L,SEEK_END); //将位置指针移动到离文件尾部前20字节处

举例:修改学生成绩

我们定义了一个 Student 结构体来表示学生的信息。在文件读取过程中,我们使用 sscanf 函数将每行信息解析为一个 Student 结构体。
然后,我们比较该结构体的 ID、name 和 score 字段来找到目标学生信息。
最后,我们修改该结构体的 score 字段,使用 fseek 函数将文件指针移动回原来的位置,并使用 fprintf 函数将修改后的学生信息写回文件。

//读取信息,修改学生成绩
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 定义一个学生的结构体
typedef struct {
    int ID;
    char name[20];
    int score;
} Student;

int main() {
    FILE *fp;
    char line[100];
    Student student;

    // 以文本方式打开文件
    if ((fp = fopen("file.txt", "r+")) == NULL) {
        fprintf(stderr, "Can't open file!\n");
        exit(1);
    }

    // 逐行读取文件内容,找到目标学生并修改信息
    while (fgets(line, sizeof(line), fp) != NULL) {
        // 将该行信息解析为一个学生结构体
        sscanf(line, "%d %s %d\n", &student.ID, student.name, &student.score);

        // 如果找到目标学生,则修改分数并将新的信息写回文件
        if (student.ID == 2 && strcmp(student.name, "Jack") == 0 && student.score == 80) {
            // 输出原来的学生信息
            printf("Original Student ID: %d\n", student.ID);
            printf("Original Student Name: %s\n", student.name);
            printf("Original Student Score: %d\n", student.score);

            // 修改成绩
            student.score = 90;

            // 将文件指针移动回原来的位置
            if (fseek(fp, -(long)strlen(line), SEEK_CUR) == 0) {
                // 将修改后的学生信息写回文件
                fprintf(fp, "%d %s %d\n", student.ID, student.name, student.score);
            }

            break;
        }
    }

    // 关闭文件
    fclose(fp);

    return 0;
}

运行结果:
在这里插入图片描述
在这里插入图片描述

  • 方法二:结构体数组实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_SIZE 100

struct Student {
    int id;
    char name[20];
    float score;
};

int main() {
    struct Student students[MAX_SIZE]; // 学生记录的数组
    int count = 0;                     // 学生记录的数量
    int target_id;                     // 要查询的学生 ID
    int i;

    // 从文件中读入学生记录
    FILE *fp = fopen("students.txt", "r");
    while (count < MAX_SIZE && fscanf(fp, "%d %s %f", &students[count].id, students[count].name, &students[count].score) == 3) {
        count++;
    }
    fclose(fp);

    // 查询指定学生记录
    printf("请输入要查询的学生 ID:");
    scanf("%d", &target_id);
    for (i = 0; i < count; i++) {
        if (students[i].id == target_id) {
            printf("学生 ID:%d\n姓名:%s\n分数:%.2f\n", students[i].id, students[i].name, students[i].score);
            students[i].score = 100.0; // 修改分数为 100
            break;
        }
    }
    if (i == count) {
        printf("没有找到 ID 为 %d 的学生记录\n", target_id);
    }

    // 将修改后的学生记录写回文件
    fp = fopen("students.txt", "w");
    for (i = 0; i < count; i++) {
        fprintf(fp, "%d %s %.2f\n", students[i].id, students[i].name, students[i].score);
    }
    fclose(fp);

    return 0;
}

运行结果:
在这里插入图片描述在这里插入图片描述

位置指针复位函数rewind

使文件位置指针返回到文件的首地址

  • 定义格式: rewind(文件指针)

此函数没有返回值

举例:上次fread和fwrite中的举例读写学生信息(改成普通文本)

fprintf写入文件数据后,需要把文件位置指针移到首位,然后读

#include <stdio.h>
#include <stdlib.h>

// 定义结构体类型
struct student
{
    char name[10];
    int score;
} stu;

int main()
{
     int n = 5; // 读取前5个结构体数组元素
     FILE* fp = fopen("student.txt", "w+"); 
    FILE* fp1 = fopen("studentout.txt", "w"); 

    if (fp == NULL) {
        printf("文件打开失败!\n");
        return -1;
    }

  struct student stuArr[10] = {
        { "Tom", 80 },
        { "Jerry", 90 },
        { "Alice", 75 },
        { "Bob", 85 },
        { "David", 92 }
    };
    for (int i = 0; i < n; i++)
    {
        fprintf(fp,"%s %d\n",stuArr[i].name, stuArr[i].score);
    }
    
   
    // 读取结构体数组
     rewind(fp);// 将文件指针移动到文件开头
    for (int i = 0; i < n; i++)
    {
        fscanf(fp, "%s %d", stuArr[i].name, &stuArr[i].score);
    }

    // 输出读取到的结构体数组
    for (int i = 0; i < n; i++)
    {
        printf("name: %s, score: %d\n", stuArr[i].name, stuArr[i].score);
    }
     // 将读取到的结构体数组写入文件
    for (int i = 0; i < n; i++)
    {
        fprintf(fp1, "%s %d\n", stuArr[i].name, stuArr[i].score);
    }
    fclose(fp);
    fclose(fp1);
    return 0;
}

返回文件当前位置的函数ftell()

  • 定义格式:ftell(文件指针);

操作成功后,返回文件位置指针的当前位置(用相对文件头的偏移量表示L) long型
操作失败返回-1L

  • 举例:用来读取文本文件中的前5个字符,然后将文件指针向前移动2个字符,最后再读取2个字符并输出:
#include <stdio.h>

int main()
{
    FILE *fp = fopen("input.txt", "r");
    if (fp == NULL)
    {
        perror("文件打开失败");
        return -1;
    }

    char c1, c2, c3, c4, c5;
    c1 = fgetc(fp);
    c2 = fgetc(fp);
    c3 = fgetc(fp);
    c4 = fgetc(fp);
    c5 = fgetc(fp);

    long int pos1 = ftell(fp); // 获取文件指针的位置
    printf("当前文件指针的位置为 %ld\n", pos1);

    fseek(fp, 2, SEEK_CUR); // 将文件指针向前移动2个字符

    long int pos2 = ftell(fp); // 获取文件指针的位置
    printf("当前文件指针的位置为 %ld\n", pos2);

    char c6, c7;
    c6 = fgetc(fp);
    c7 = fgetc(fp);

    printf("%c%c\n", c3, c4);
    printf("%c%c\n", c6, c7);

    fclose(fp);
    return 0;
}

在这里插入图片描述

在这里插入图片描述

文件检测

检测文件结束函数feof()

  • 格式:feof(文件指针)
    函数feof用来判断fp指针是否已经到达文件末尾,用于判断文件是否结束。

  • 返回值

结束位置返回1
不是结束位置返回0

  • 注意只能用于文本文件,不能用于二进制文件

 

  • 举例:从文件中读取整数并求和,直到文件结尾为止:
#include <stdio.h>

int main()
{
    FILE *fp = fopen("input.txt", "r");
    if (fp == NULL)
    {
        printf("文件打开失败!\n");
        return -1;
    }

    int sum = 0;
    int num;
    while (!feof(fp)) // 当没有到达文件结尾时继续读取
    {
        if (fscanf(fp, "%d", &num) == 1)
        {
            sum += num;
        }
    }
    printf("和为:%d\n", sum);

    fclose(fp);
    return 0;
}

运行结果:

在这里插入图片描述
 


检测文件操作错误函数ferror

  • 定义:ferror(文件指针);

函数用来检查文件在用各种输入输出函数进行读写是否出错

  • 返回值

没出错返回0
出错返回非0

  • 注意事项:

(1) 对同一文件,每次调用输入输出函数均产生一个新的ferror()函数值。因此在调用了输入输出函数后,应立即检测,否则出错信息会丢失。

(2)在执行fopen()函数时,系统将ferror()的值自动置为0。(没有进行文件操作)

那么如果出错咋办?

要立即进行清理错误——clearerr

清除错误标志函数clearerr()

用于清除出错标志,把它们都置为0

当调用读写操作出错后,ferror的值为非0,可以通过clearerr清除错误标志,使它ferror的返回值为0。

读写错误并清理的例子 (ferror&clearerr)

以下是一个读取文件数据并检测错误的例子:

#include <stdio.h>

int main() {
    FILE* fp = fopen("example.txt", "r");  //打开文件
    if (fp == NULL) {
        printf("Failed to open file.\n");
        return -1;
    }

    char buffer[100];
    size_t result = fread(buffer, 1, 10, fp);  //读取文件数据
    if (result != 10) {
        if (ferror(fp)) {  // 检测错误标志
            printf("Error reading file.\n");
            clearerr(fp);  //清除错误标志
        } else if (feof(fp)) {
            printf("End of file reached.\n"); //如果到达文件结尾
        }
    }

    fclose(fp); // 关闭文件
    return 0;
}

 


文件的简单复制

试试看: 已知一个文本数据文件f1.txt,请将该文件复制一份,保存为f2.txt。

分析问题:
新建一个文本文件f1.txt,将该文件与源程序放在同一目录下,执行程序,观察结果。

思路:利用读写操作进行复制等操作,要注意进行判空feof

#include <stdio.h>

int main()
{
    FILE *fp1, *fp2;
    char ch;
    fp1 = fopen("test1.txt", "r");//以读方式打开test1.txt
    fp2 = fopen("test2.txt", "w");//以写方式打开test2.txt
    while (feof(fp1)== 0) //fp1没有结束时候
    {
        ch = fgetc(fp1);//从fp1中读取一个字符
        //如果到达文件结尾,结束循环
        if (feof(fp1) != 0)
            break;
            
        fputc(ch, fp2);//把ch写入fp2
    }
   
    fclose(fp1);
    fclose(fp2);
    return 0;
}
   //如果到达文件结尾,结束循环
    if (feof(fp1) != 0)
        break;

是为了防止读取test1.txt文件时到达文件结尾,那么fgetc()函数会返回EOF(即-1),程序会把这个EOF作为ch的值写入test2.txt文件

运行结果:

在这里插入图片描述

 


关于文件的操作进行多练习,后续文件项目看专题C语言项目实现里面的简单的管理系统

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值