本章主要介绍最后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文件目录下
文件的概念
文件:保存在外存储器上的一组数据的有序集合
特点:
- 数据
长久保存
- 数据
长度不变
- 数据
按顺序存取
逻辑文件
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表示二进制文件
下面是一些注意事项:
- 如果以“读”方式打开文件,指定文件必须存在,否则出错;
- 如果以“写”方式打开文件,则指定的文件可以存在,也可以不存在
- 如果 以"w"方式写,如果 该文件已经存在,则原文件将被删去重新建立; 否则,按指定的名字新建一个文件;
- 如果 以"a"方式写,如果 该文件已经存在,写入的数据将被添加到指定文件原有数据的后面,不会删去原来的内容:否则,按指定的名字新建一个文件(与“w”相同)
- 如果 文件同时读和写,使用"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” 中,这样一来,我们就可以方便地查看程序的错误信息。
三种标准文件存在的问题
使用标准输入输出设备文件存在的问题主要有以下几点:
- 从键盘输入数据时,用户输入的数据只能用一次。
- 输出到屏幕的信息只能看不能编辑。
为了解决这些问题,才提出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语言项目实现里面的简单的管理系统
文章评论