目录
1、为什么使用文件
我们所写的程序运行起来的输入(读)的数据是保存在内存上的,而内存里面的数据不会持久保存,我们再将程序运行起来,发现上一次输入的数据信息丢失了,又得重新输入,在有些场景下我们不需要保存前面的数据,而有些场景下就需要我们持久化的保存以前输入的数据信息,此时我们就要将这些数据信息保存到硬盘(外存)中,存放在硬盘中的是以文件的形式保存的,此时我们就要使用到文件了,使用文件我们可以将数据信息直接存放在电脑硬盘上,做到了数据的持久化!
2、什么是文件
磁盘(硬盘)上的文件就是文件,但在程序设计中,我们所说的文件一般有两种:程序文件、数据文件(从文件功能角度来分类)!
程序文件
就是我们所用的源程序文件(后缀名为 .c),目标文件(Windows环境后缀名.obj),可执行程序(Windows环境后缀名 .exe)等.. 这些文件被称为程序文件!
数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,数据文件就是,程序运行时,需要从中读取数据的文件 或者 输出内容的文件!本篇我们讨论的就是数据文件,在以前我们所处理的输入输出都是以终端为对象,即从终端的键盘输入数据,运行结果输出到显示器上!其实有时候我们会把信息输出(写入)到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上的文件!
文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用、它就是文件名。
文件名包含三个部分:文件路径+文件主干+文件后缀
例如:D:\vstiu\test.c
其中:D:\vstiu\是文件路径、test是文件主干、.c是文件后缀名
为了方便起见,通常将文件主干(文件标识)称为文件名!
3、文件的打开和关闭
文件指针
每个被使用的文件都在内存中开辟的了一个相对应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前位置等)。这些信息是保存在一个结构体变量中的,该结构体类型是由系统来声明的,给该结构体取名为FILE,而FILE类型定义的指针变量,就被称为文件指针!
在vs2013编译环境提供的stdio.h头文件中该结构体的类型的声明为:
但在vs2019编译环境提供的该结构体类型声明为:
vs2019中用了一个指针来代替了结构体中的具体类型
不同C编译器FILE类型包含的内容不完全相同,但是大同小异!
每当打开一个文件的时候,系统会根据文件的情况在内存中自动创建一个FILE类型的结构变量,一般我们都是通过FILE类型的指针来维护这个FILE结构变量!
下面定义一个FILE类型的指针:FILE* pf(文件指针变量)
FILE*其实就是一个文件指针类型,pf是一个指向FILE类型的指针变量(文件指针变量),可以使pf指向某个文件的文件信息区(结构体变量),通过该文件信息区就能访问到该文件,也就是说,通过文件指针变量可以找到与它关联的文件!
看图了解:
其中pf1、pf2、pf3 都是FILE类型的指针变量也就是文件指针,分别指向 f1、f2、f3 三个文件的文件信息区,通过文件信息区可以访问该文件!
文件的打开和关闭
既然我们要通过指向文件信息区的文件指针来操作文件,那么我们就得先打开文件,以供我们操作,在使用结束之后我们也要关闭文件,那么究竟该如何打开和关闭文件呢?在C语言中提供了一个打开文件的函数以及关闭文件的函数,供我们使用,我们首先仔细了解一下这两个函数:
1、打开文件fopen:
函数原型: FILE * fopen ( const char * filename, const char * mode );
返回值 :FILE *
返回一个FILE型的指针
函数参数:
第一个参数是一个字符串
表示的是要打开的文件名
第二个参数是一个字符串
表示的是打开文件之后进行的操作
函数作用:
用fopen打开一个文件,第一个参数是要打开的文件名,第二参数是对该文件的打开方式(也就是打开之后对该文件的操作方式),返回值:若文件打开成功则返回该文件所对应的文件信息区的首地址,若文件打开失败则返回NULL!
2、关闭文件fcloes:
函数原型:int fclose ( FILE * stream );
返回值:int
返回一个整数
函数参数:FILE * stream
参数是一个文件指针
函数作用:
用fclose关闭一个文件,参数是指向要关闭的文件信息区的文件指针,返回值:若该文件关闭成功则返回数字0,若该文件关闭失败则返回EOF(-1)
文件有几种不同的打开(操作)方式:
具体应用,看如下代码:
#include <stdio.h>
int main()
{
//打开文件
FILE* pf = fopen( "test.txt", "w"); //以写数据的形式打开当前目录低下的test.txt文件
//W的形式 若该文件不存在会新建一个该文件!
//判断文件是否打开成功
if ( NULL == pf)
{
perror( "fopen");
return;
}
//进行写操作
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
4、文件的顺序读写
文件的顺序读写,顾名思义就是按照顺序一一读写,那么什么是读又什么是写呢? 对于这里我们仔细介绍一下文件的读和写!
文件读写介绍
流的概念:
在进行一系列输入输出操作的时候会产生一个数据流,C语言在一开始的时候,会主动准备三个流供我们使用,:stdin(标准输入流) stdout(标准输出流) stderr(标准错误流),那么我们可以这样理解读和写,读就是输入数据,写就是输出数据,我们知道我们常用的scanf函数就是一个典型的输入函数,也是读函数,就是从键盘上读入数据,而写就是输出数据,常用的printf函数,就是输出函数,也是写函数,就是将键盘上读到的数据写到屏幕上,从而可以显示到屏幕上,再详细说,就是scanf函数会从标准输入(stdin)(输入设备:键盘等...)中读数据,将数据格式化的读到内存中,printf函数会将读入到内存中的数据,格式化的写到标准输出(sidout)(输出设备:屏幕 ,打印机 等....)上!而输入输出设备我们可以认为是键盘和屏幕!
画图理解:
scanf函数跟print函数只针对的是 标准输入流(stdin),标准输出流(stdout),不能适用于对文件的操作!
文件读写函数
在C语言中提供了一些,可以对文件进行读写的函数,可以在文件里面写数据,也可将文件里面的数据读出来,下面就带大家一一认识一下这些函数
文件读写函数一一介绍:
fputc(字符 输出/写 函数)
函数原型:int fputc ( int character, FILE * stream );
函数参数:int character, FILE * stream
第一个参数是一个整数
表示的是字符的ASICC码值
第二个参数是文件指针
表示的是要写入字符的文件
返回值:int
是一个整数
写入成功返回该字符的ASICC值
函数作用:
第一个参数表示的是要写入的字符的ASICC码值,第二个参数表示的是一个指向要写入的文件的文件信息区的文件指针,若写入成功则返回该字符本身(也就是返回字符的ASICC值),若写入失败则返回EOF(-1),该函数就是将第一个参数字符,写到第二个参数指向的文件中!
函数应用看代码:
#include <stdio.h>
int main()
{
//打开文件
FILE* pf = fopen( "test.txt", "w"); //以写数据的形式打开当前目录低下的test.txt文件
//W的形式 若该文件不存在会新建一个该文件!
//以w写的形式打开文件,该文件之前的内容会被新内容覆盖
//可以理解为将原来文件里的内容给清空了!
//判断文件是否打开成功
if ( NULL == pf)
{
perror( "fopen");
return;
}
//进行写操作
fputc( 'a', pf); //将字符a写到pf指向的test.txt文件中
fputc( 'b', pf); //将字符b写到pf指向的test.txt文件中
fputc( 'c', pf); //将字符c写到pf指向的test.txt文件中
//注意:在写的时候是按照顺序写,先写a再写b以此类推!
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fgetc(字符 输入/读 函数)
函数原型:int fgetc ( FILE * stream );
函数参数:FILE * stream
是一个文件指针
表示要操作的文件的文件指针
返回值:int
是一个整型数
读取成功返回读取的字符
函数作用:
fgetc一次只能读取一个字符,参数是文件指针,表示要读取的文件,返回值:若读取字符成功则返回该字符(ASICC值)若读取失败返回EOF,若读取到文件末尾也会返回EOF,读取不成功也会返回EOF,但会设置其错误指示符(ferror)!
函数应用看代码:
#include <stdio.h>
int main()
{
//打开文件
FILE* pf = fopen( "test.txt", "r"); //以读数据的形式打开当前目录低下的test.txt文件
//"r" 读操作的形式打开文本文件若要读的文件不存在会报错
//判断文件是否打开成功
if ( NULL == pf)
{
perror( "fopen");
return;
}
//进行读操作
int ret = 0;
ret = fgetc(pf); //读出a用ret接收
printf( "%c\n", ret); //打印
ret = fgetc(pf); //读出b用ret接收
printf( "%c\n", ret); //打印
ret = fgetc(pf); //读出c用ret接收
printf( "%c\n", ret); //打印
//读完文件里的字符,再读就读到了文件尾EOF (-1)
ret = fgetc(pf); //读出用ret接收
printf( "%d\n", ret); //打印
//注意:在读的时候是按照顺序写,先读a再写b以此类推!
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fputs(文本行 输出/写 函数)
函数原型:int fputs ( const char * str, FILE * stream );
函数参数:const char * str, FILE * stream
第一个参数是一个字符串
用const修饰说明其值不可变
第二个参数是一个文件指针
表示要写入数据的文件
返回值:int
是一个整型数
写入成功返回一个非负值
函数作用:
参数一是一个指向字符串的指针,参数二是一个指向文件信息区的文件指针,函数的作用就是将字符串写到文件指针对应的文件中,返回值是一个整型,若写入成功则返回一个非负数,若写入失败则返回EOF,并设置错误指示器(ferror)
函数应用看代码:
#include <stdio.h>
int main()
{
//打开文件
FILE* pf = fopen( "test.txt", "w"); //写操作
//判断是否打开成功
if ( NULL == pf)
{
perror( "foren");
return 1;
}
//写数据
//文本行写入数据
fputs( "abcdefg\n", pf); //将一行字符串写进去
fputs( "hello world!\n", pf); //再写一行字符串
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fgets(文本行 输入/读 函数)
函数原型:char * fgets ( char * str, int num, FILE * stream );
函数参数:char * str, int num, FILE * stream
第一个参数是指向字符串的指针
第二个参数是一个整型数
第三个参数是一个文件指针
返回值:char *
返回一个字符指针
函数作用:
参数一是一个字符串,参数二表示读取的个数,参数三是文件指针,通俗来说:就是将文件里的num个字符的内容读到字符串str中,注意num个字符中读取够的时候会自动加一个‘\0’进去,所以实际上读过去的字符应该是num-1个。返回值:读取成功返回的是str字符串,读取失败返回NULL,且要设置错误指示器(ferror)!。fgets在读取的时候是按行读取的,只要一行结束即使没有读够num-1个字符,也会结束读取,只要行结束就读取结束!
若读取的num个数不够一行再次读取的时候会继续往后读取,直到这一行读取完,才进入下一行!
函数应用看代码:
#include <stdio.h>
int main()
{
//打开文件
FILE* pf = fopen( "test.txt", "r"); //读操作
//判断是否打开成功
if ( NULL == pf)
{
perror( "foren");
return 1;
}
//读数据
//文本行读入数据
char str[ 20];
fgets(str, 9, pf); //读9个字符到str中
printf( "%s", str);
fgets(str, 12, pf); //再读10个字符到str
printf( "%s", str);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fprintf(格式化 输出/写 函数)
函数原型:int fprintf ( FILE * stream, const char * format, ... );
函数参数:FILE * stream, const char * format, ...
第一个参数是文件指针
后面是一个可变参数列表
返回值:int
是一个整型数
函数作用:
fprintf通俗点说就是将可变参数列表里格式化的数据写入到文件中,fprint其实跟print函数相似只是多了一个文件指针的参数,其区别就是print函数只能写入到标准输出流中,而fprintf即可以写入到标准输出流,又可以写入到文件中,但两种都是格式化的输出!返回值:写入成功则返回写入字符的总个数,写入失败会返回一个负数!
函数应用看代码:
#include <stdio.h>
struct stu
{
char name[ 20];
int age;
float salary;
};
int main()
{
struct stu s = { "张三", 25, 1.5 };
//打开文件
FILE* pf = fopen( "test.txt", "w");
//判断是否打开成功
if ( NULL == pf)
{
perror( "fopen");
return 1;
}
//格式化写数据
fprintf(pf, "%s %d %f\n", s.name, s.age, s.salary);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fscanf(格式化 输入/读 函数)
函数原型:int fscanf ( FILE * stream, const char * format, ... );
函数参数:FILE * stream, const char * format, ...
第一个参数是文件指针
后面的是可变参数列表
返回值:int
返回值是一个整型数
函数作用:
通俗些说就是将文件里的内容格式化的读到可变参数列表中,返回值:读取成功则返回读取成功的参数个数,读取失败则返回EOF,fscanf其实跟scanf函数相似只是多了一个文件指针的参数,其区别就是scanf函数只能读标准输入流中的数据,而fscanf即可以读取标准输入流中的数据,又可以读取到文件中的数据,但两种都是格式化的输入
函数应用看代码:
#include <stdio.h>
struct stu
{
char name[ 20];
int age;
float salary;
};
int main()
{
struct stu ss = { 0 };
pf = fopen( "test.txt", "r");
//判断是否打开成功
if ( NULL == pf)
{
perror( "fopen");
return 1;
}
//格式化读数据
fscanf(pf, "%s %d %f\n", ss.name, &(ss.age), &(ss.salary)); //把上面写入的数据读出来,读到结构体变量ss中
printf( "%s %d %f\n", ss.name, ss.age, ss.salary); //打印看是否读取成功
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fwrite(二进制 输出/写 函数)
函数原型:size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
函数参数:const void * ptr, size_t size, size_t count, FILE * stream
第一个参数是任意类型的指针
第二个参数是一个无符号整数
第三个参数也是无符号整数
第四个参数是文件指针
返回值:size_t
返回值是一个无符号整数
函数作用:
ptr是一个任意类型的指针,size表示写入文件的数据的大小,count表示一次要写几个size大小的数据表示个数,最后就是指向文件信息区的文件指针!,通俗说就是将ptr指向的空间里的内容,一次写size大小的数据,总共写count次,写到文件中!fwrite函数只能对文件进行写操作,返回值:写入成功返回写入元素的总数!
函数应用看代码:
#include <stdio.h>
struct stu
{
char name[ 20];
int age;
float salary;
};
int main()
{
struct stu s = { "王麻子", 23, 2.8 };
struct stu* pc = &s;
//打开文件
FILE* pf = fopen( "test.txt", "wb"); //wb二进制的写操作
//判断文件是否打开成功
if ( NULL == pf)
{
perror( "fopen");
return 1;
}
//进行二进制的写操作
fwrite(pc, sizeof(s), 1, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fread(二进制 输入/读 函数)
函数原型:size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
函数参数:void * ptr, size_t size, size_t count, FILE * stream
第一个参数是任意类型的指针
第二个参数是无符号整型
第三个参数也是无符号整型
第四个参数是文件指针
返回值:size_t
返回的是一个无符号整型
函数作用:
就是将文件中的二进制数据内容,一次按照size大小,总共读取count次,读到ptr指向的空间中!,fread 函数只能对文件进行读操作!返回值:读取成功返回的是读取成功元素的个数!
函数应用看代码:
#include <stdio.h>
struct stu
{
char name[ 20];
int age;
float salary;
};
int main()
{
struct stu s = { 0};
struct stu* pc = &s;
//打开文件
FILE* pf = fopen( "test.txt", "rb"); //rb二进制的读操作
//判断文件是否打开成功
if ( NULL == pf)
{
perror( "fopen");
return 1;
}
//读取二进制文件
fread(pc, sizeof( struct stu), 1, pf); //将文件内容读到pc指向的空间里面去
printf( "%s %d %f\n", pc->name, pc->age, pc->salary); //将读到的内容打印出来
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
对比一组函数
scanf、fscanf、sscanf
printf、fprintf、sprintf
区别:
scanf是格式化输入(读)函数,它针对的是标准输入(stdin)
fscanf是格式化读函数,它即可以针对标准输入,也可以针对文件
printf是格式化输出(写)函数,它针对的是标准输出(stdout)
fprintf是格式化写函数,它即可以针对标准输入,也可以针对文件
但对于sscanf 和 sprintf 函数我们还不了解,下面我们来认识一下这两个函数
sscanf 函数
函数原型:int sscanf ( const char * s, const char * format, ...);
函数参数:const char * s, const char * format, ...
第一个参数是指向字符串的指针
其它的是可变参数列表
返回值:int
返回值是一个整型数
函数作用:将s指向的空间里的字符串的内容,格式化的读取出来,后面的可变参数列表的格式化数据是自己设置的,返回值:读取成功返回读取成功的个数,读取失败返回EOF!
函数用途看代码:
#include <stdio.h>
struct stu
{
char name[ 20];
int age;
float clcer;
};
int main()
{
struct stu s = { 0 };
char* str = "张三 25 2.36";
//运用sscanf读字符串函数
//将str中的内容格式化的读入到结构体变量s中
sscanf(str, "%s %d %f", s.name, &(s.age), &(s.clcer));
//将读取到的内容打印出来
printf( "%s %d %f\n", s.name, s.age, s.clcer);
return 0;
}
sprintf 函数
函数原型:int sprintf ( char * str, const char * format, ... );
函数参数:char * str, const char * format, ...
第一个参数是指向字符串的指针
其它的是可变参数列表
返回值:int
返回的是一个整型数
函数作用:
就是将后面格式化的数据写到str指向的字符串空间中,通俗来说就是将一些格式化的数据转化为字符串!返回值:写入成功时则返回写入的字符的总个数,写入失败则返回一个负数!
函数用途看代码:
#include <stdio.h>
struct stu
{
char name[ 20];
int age;
float hight;
};
int main()
{
struct stu s = { "奥特曼", 1880, 10.25 };
char str[ 30];
//将结构体变量s的内容写入到字符串str空间中
sprintf(str, "%s %d %f", s.name, s.age, s.hight);
//将写入的内容打印出来
printf( "%s\n", str);
return 0;
}
总结:
scanf 和 printf 是针对标准输入输出流的!
fscanf 和 fprintf 即可以针对标准输入输出流,又可以针对文件操作!
sscanf 和 sprintf 是针对字符串的!
5、文件的随机读写
经过上述的了解,我们知道在对文件进行读写操作的时候都是按照顺序读写的,要么就是一行一行的按顺序读写,要么就是一个一个的按顺序读写!那么我们有时候的需求不仅仅是按照顺序读写,而是要只读写某个位置或者只读写某行的内容,此时上述的顺序读写就不能满满足我们的要求了,这种读写方式我们称为随机读写!为了满足我们的这一需求C语言给我们提供了三个函数来供我们使用,下面我们一一介绍了解一下这三个函数!
fseek
根据文件指针的位置和偏移量来定位文件指针!
fseek函数
函数原型:int fseek ( FILE * stream, long int offset, int origin );
函数参数:FILE * stream, long int offset, int origin
第一个参数是文件指针
表示要操作的文件
第二个参数是一个长整型数
表示文件指针的偏移量
第三个参数是一个整型数
表示从哪个位置开始是一个宏
返回值:int
是一个整型数
函数作用:
就是根据文件指针的位置,和偏移量来定位文件指针,在此处我们要了解表示位置的 三个宏常量:
SEEK_SET(文件开头),
SEEK_CUR(文件指针的当前位置),
SEEK_END(文件结束),
通俗解读:
第一个参数是指向文件信息区的文件指针,第二个参数是指针的偏移量,偏移量中正数表示向右偏移,负数表示向左偏移。第三个参数表示的是位置,表示从哪个位置开始偏移,第三个参数就要用到上面的三个宏常量了!
返回值:如果成功则返回数字0,如果失败则返回非0数!
函数应用看代码:
#include <stdio.h>
int main()
{
//打开文件
FILE* pf= fopen( "test.txt", "w"); //以写形式打开
//判断是否打开成功
if ( NULL == pf)
{
perror( "fopen");
return 1;
}
//按行写将一行字符串写入到pf指向的文件信息区所对应的文件中!
fputs( "This is an apple.", pf);
//让文件指针从文件开头向右偏移9字节
fseek(pf, 9, SEEK_SET);
//在偏移量为9的位置再写入一个字符串
//在写入的时候会将后面的数据覆盖掉!
fputs( " sam", pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
ftell
返回文件指针相对于起始位置的偏移量
ftell函数
函数原型:long int ftell ( FILE * stream );
函数参数:FILE * stream
参数是一个文件指针
返回值:long int
返回长整型数
函数作用:
就是计算出文件指针当前位置,相对于起始位置的偏移量,对于二进制流返回的是从起始位置开始的字节数,返回值:成功则返回当前位置与起始位置之间的偏移量,失败则返回-1L
函数用途看代码:
#include <stdio.h>
int main()
{
FILE* pf= NULL;
long size= 0;
//打开文件
pf = fopen( "test.txt", "rb"); //以二进制的读的形式打开
//判断是否打开成功
if (pf == NULL)
{
perror( "Error opening file");
return 1;
}
//让指针的位置偏移到最后的位置(也就是文件尾部)
fseek(pf, 0, SEEK_END);
//计算出相对于起始位置的偏移量
size = ftell(pf);
//关闭文件
fclose(pf);
pf = NULL;
//打印出偏移量
printf( "Size of test.c: %ld bytes.\n", size);
return 0;
}
rewind
让文件指针的位置回到文件的起始位置
rewind函数
函数原型:void rewind ( FILE * stream );函数参数:FILE * stream
参数是一个指向文件信息区的文件指针
返回值:void
无返回值
函数作用:
就是将文件指针的指向改变,让其回到起始位置!,让文件指针的位置回到文件的起始位置!
函数用途看代码:
#include <stdio.h>
int main()
{
FILE* pFile;
char buffer[ 27];
//打开文件
pFile = fopen( "test.txt", "w+"); //以读写的形式打开
//判断是否打开成功
if ( NULL == pFile)
{
perror( "fopen");
return 1;
}
//写数据,将字符'A'到'Z’写到文件中
int i = 0;
for (i = 'A'; i <= 'Z'; i++)
{
fputc(i, pFile);
}
//让文件指针回到文件起始位置
rewind(pFile);
//将文件里的内容从头开始一次读一个字节,总共读取26次,读到buffer字符串中
fread(buffer, 1, 26, pFile);
//读取完之后在最后加上'\0'字符
buffer[ 26] = '\0';
//打印出buffer里的内容
printf( "%s\n", buffer);
//关闭文件
fclose(pFile);
pFile = NULL;
return 0;
}
6、文本文件和二进制文件
根据数据的组织形式,将数据文件分为:文本文件和二进制文件!
二进制文件:
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件
文本文件:
数据在内存中以二进制的形式存储,如果要求在外存上以ASCII码的形式存储,则需要在存储前转换,以ASICC字符的形式存储的文件就是文本文件!
一个数据在内存中如何存储:
字符一律以ASCII码形式存储,数值型数据既可以用ASICC形式存储,也可以使用二进制存储,假设由数值 10000,若以ASICC码形式存储总共占4个字节,若用二进制存储总共占4个字节!
举个例子:
10000的在内存中的存储:
用ASICC值存放就是文本文件,而用二进制不加以转换直接存放的就是二进制文件
代码测试:
#include <stdio.h>
int main()
{
//将10000写入到文件中
int a = 10000;
//打开文件
FILE* pf = fopen( "test.txt", "wb");
//判断文件是否打开成功
if ( NULL == pf)
{
perror( "fopen");
return 1;
}
fwrite(&a, 4, 1, pf); //二进制的形式写到文件中
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
画图解析:
7、文件读取结束的判定
被错误使用的feof
认识feof函数:
函数原型:int feof ( FILE * stream );
函数参数:FILE * stream
参数是一个指向文件信息区的文件指针
返回值:int
返回一个整型数
函数作用:
本质上就是文件在读取结束后,判断是否是因为读到文件尾而结束,返回值:若是因为遇到文件尾结束返回非零数,否则返回数字0
牢记:在文件读取过程中,不能使用feof函数的返回值直接来判定文件是否结束,而是应用于当文件读取结束的时候,判断是读取失败结束还是遇到文件尾结束!
1、判断文本文件是否读取结束:
文件文件是否读取结束,只需要判断返回值是否是EOF(fgetc) ,或者是NULL(fgets)
fegtc读取失败返回:EOF
fgets读取失败返回:NULL
在文本文件中应用feof函数:如下例子:
#include <stdio.h>
#include <stdlib.h>
int main()
{
//打开文件
FILE* pf = fopen( "test.txt", "r");
//判断是否打开成功
if ( NULL == pf)
{
perror( "fopen");
return 1;
}
//进行读文件
int c = 0;
//一个字符一个字符的进行读取,读取成功会返回该字符的ASICC码值
while ((c = fgetc(pf)) != EOF) //当fetc读取失败的时候会返回EOF,读到文件尾也是EOF
{
printf( "%c ", c);
}
printf( "\n");
//读完之后判断是什么原因结束的
if ( ferror(pf)) //是发生错误读取结束
{
printf( "发生错误\n");
}
else if ( feof(pf)) //是否是读到文件尾结束
{
printf( "文件数据读完了\n");
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
2、判断二进制文件是否读取结束
二进制文件是否读取结束:判断返回值是否小于实际要读到的个数!
fread函数判断返回值是否小于读取的个数
在二进制文件中应用feof函数:如下例子:
#include <stdio.h>
#define SIZE 5
int main()
{
//二进制写数据
double a[SIZE] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
//打开文件
FILE* fp = fopen( "test.bin", "wb"); // 以二进制写的形式打开
//判断是否打开成功
if ( NULL == fp)
{
perror( "fopen");
return 1;
}
//写数据
fwrite(a, sizeof(a[ 0]), SIZE, fp); // 将数组a的内容写到文件中
//关闭文件
fclose(fp);
fp = NULL;
//二进制读数据
double b[SIZE];
//打开文件
fp = fopen( "test.bin", "rb"); //二进制读的形式打开
//判断是否打开成功
if ( NULL == fp)
{
perror( "fopen");
return 1;
}
size_t ret = fread(b, sizeof(b[ 0]), SIZE, fp); // 将文件的内容读到b中
//判断是否读取成功!
if (ret == SIZE) //读取成功!
{
//将读到的内容打印出来
for ( int i = 0; i < SIZE; ++i)
{
printf( "%f ", b[i]);
}
putchar( '\n');
}
else //读取失败
{
//判断失败原因
if ( feof(fp)) //是否是因为读到文件尾而读取结束
{
printf( "Error reading test.bin: unexpected end of file\n");
}
else if ( ferror(fp)) //是否是因为出错而读取结束
{
perror( "Error reading test.bin");
}
}
//关闭文件
fclose(fp);
fp = NULL;
return 0;
}
8、文件缓冲区
ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序 中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装 满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓 冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根 据C编译系统决定的。画图理解: 缓冲区测试代码:
缓冲区测试代码:
#include <stdio.h>
#include <windows.h>
int main()
{
//打开文件
FILE* pf = fopen( "test.txt", "w");
//判断是否打开成功
if ( NULL == pf)
{
perror( "fopen");
return 1;
}
fputs( "abcdef", pf); //先将代码放在输出缓冲区
printf( "睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
Sleep( 10000); //休眠10s
printf( "刷新缓冲区\n");
fflush(pf); //刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
//注:fflush 在高版本的VS上不能使用了
printf( "再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");
Sleep( 10000);
fclose(pf);
//注:fclose在关闭文件的时候,也会刷新缓冲区
pf = NULL;
return 0;
}总结:因为由缓冲区存在,所以C语言在操作文件的时候,需要做刷新缓冲区,或者在文件操作结束的时候关闭文件(关闭文件也会刷新缓冲区),如果不做,可能导致写文件的问题
转载:https://blog.csdn.net/dd811923461/article/details/128756414
 
					







