当前位置:首页 » 《休闲阅读》 » 正文

C语言篇 +文件操作(营养鸡汤期末不挂科)_m0_53421868的博客

12 人参与  2022年02月12日 17:06  分类 : 《休闲阅读》  评论

点击全文阅读


在这里插入图片描述

目录

  • 什么是文件
  • 文件名
  • 文件类型
  • 文件缓冲区
  • 文件指针
    • 总结:
    • 补充:
  • 文件的打开和关闭
    • 那么如何打开桌面上的文件呢?
  • 文件的顺序读写
    • 文件读写的函数
    • 字符输出函数fputc
    • 流的概念
    • 字符输入函数fgetc
    • 总结:
    • 文本行输出函数 fputs
    • 文本行输入函数 fgets
    • 格式化输出函数 fprintf
    • 格式化输入函数 fscanf
    • 二进制输出 fwrite
    • 二进制输入 fread
    • sprintf
    • sscanf
  • 文件的随机读写
    • fseek
    • ftell
    • rewind
  • 文本文件和二进制文件
    • 总结:
    • 关于二进制文件和文本文件的区别
  • 文件结束的判定
    • 被错误使用的 feof
    • 总结:
  • 文件缓冲区
    • 验证缓冲区的存在

什么是文件

  • 什么是文件 磁盘上的文件是文件。

  • 但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件

  • 程序文件
    包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀 为.exe)。

  • 数据文件 文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内 容的文件。
    在这里插入图片描述

本章只讨论数据文件,数据文件总结一下:就是你可以用程序去向文件中(输出信息 / 写)又或者从文件中(输入信息 /读)到你的程序中去,而在这里需要站在内存的角度想清楚文件的读和写

  • 在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。
  • 其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上的文件,所以我们今天面对的输入输出对象就是文件

文件名

  • 文件名包含3部分:文件路径+文件名主干+文件后缀
  • 例如: c:\code\test.txt
  • 为了方便起见,文件标识常被称为文件名

文件类型

文件缓冲区

文件指针

  • 缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。

  • 每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及
    文件当前的位置等)。

  • 这些信息是保存在一个结构体变量中的。

  • 该结构体类型是有系统声明的,取名FILE. 例如,VS2008编译环境提供的stdio.h 头文件中有以下的文件类型申明:
    在这里插入图片描述
    在这里插入图片描述

总结:

当文件被打开的时候会自动创建一个文件信息区,文件信息区是强关联该文件的,他会记录文件信息实际上他就是一个结构体变量,struct _iobuf 是文件的本质类型,FILE是由typedef定义出来的文件类型,他也是一个文件类型,
而文件信息区的类型是一个struct FILE类型

补充:

  • 不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。
    每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关 心细节。
    一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便

  • 定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。

  • 通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件

//以只读的方式打开data文件,并返回该文件信息区的起始地址
FILE *pf1 = fopen("data1.txt,"r"");
FILE *pf2 = fopen("data2.txt,"r"");
FILE *pf3 = fopen("data3.txt,"r"");

如图所示是文件指针与文件信息区对应的关系
在这里插入图片描述

总结:文件信息区实际上是一个FILE类型的结构体变量,关于他是怎么创建的,其实是在文件被打开的时候就会自动生成,并且是关联这个文件的,最后再返回文件信息区的起始地址,有了文件信息区的起始地址就能找到文件

文件的打开和关闭

注意事项

  • 1、文件在读写之前应该先打开文件,
  • 2、在使用结束之后应该关闭文件,
  • 3、文件在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系
  • 4、ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件

文件打开函数的原型

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

filename:是打开文件名
mode 是以什么模式打开

文件关闭函数的原型

int fclose ( FILE * stream );
stream :传入一个文件指针

打开方式如下:
在这里插入图片描述

//函数功能打开一个文件
void filetest() 
{
	//以只读的方式“r”打开文件返回文件信息区的起始地址
	FILE *pf = fopen("data.txt","r");
	//如果打开失败返回NULL,就打印错误信息并退出程序
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//读文件
	//关闭文件,有点类似于free函数的写法
	flcose(pf);
	//指针置空防止成为野指针
	pf = NULL
}

int main() 
{
	filetest();
	return 0;
}

以只读的方式“r”打开 由于此时在我的工程中并没有data这个数据文件,所以会打印错误信息提示我没有此文件,说明我们的文件打开失败了
在这里插入图片描述
既然没有这个文件那么就创建一个文件
在这里插入图片描述
当我们的工程目录下多出了一个data.txt会是什么样呢
在这里插入图片描述
这个时候程序正常,并没有出错,
总结一下:
当以不指定路径的方式打开文件,默认先访问工程目录下,如果工程下找不到这个文件又以只读的方式 “r” 打开文件那么就会出错,

那么如何打开桌面上的文件呢?

其实桌面上的文件和从磁盘上打开文件并没有多大的区别,因为fopen的第一个参数只需要接受一个文件名(文件路径+文件名主干+文件后缀),和使用打开方式就行
在这里插入图片描述
这时在我的桌面上新建一个文本文件(数据文件),我们在程序中将他打开,先来观察这个文件所处的路径
在这里插入图片描述
有了文件的路径就只需要将文件路径传给fopen函数就行,而在这需要注意的是 \ 遇到字符会变成转义字符,而为了防止他成为转义字符就需要用双斜杠将 / 转义成普通的斜杠,这样才是一个完整的路径

在这里插入图片描述

void filetest() 
{
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt","r");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	fclose(pf);
	pf = NULL;
}
int main() 
{
	filetest();
	return 0;
}

那么关于以只读 “r” 的方式打开文件的举例已经够了,再来看看以“w"(只写)的方式打开文件会是怎么样的呢,这里需要注意细节
请添加图片描述
可以看到如果桌面上没有这个文件,那么以 “ w ” 只写的方式打开文件会创建一个文件,我们再来看如果文件已经存在了再以“ w ”只读的方式打开会怎么样
请添加图片描述
很明显可以看出之前的信息明显是保存了,可是再次打开这个文件中却什么都没有,看到这里其实我们也应该直到这个fopen函数的脾气了,当文件不存在就会创建一个文件,而当文件存在就会销毁该文件的内容

文件的顺序读写

当我们的程序需要写入一个数据到文件当中去的时候对应的动作是(输出/写)这里用fputc表示,当我们从文件当中读取一个数据到内存中去的时候对应的动作是(输入/读),这里用fgetc表示,
在这里插入图片描述

文件读写的函数

在这里插入图片描述

字符输出函数fputc

int fputc( int c, FILE *stream );
c:要写入到文件的字符
stream :FILE结构的指针

int fputc( int c, FILE *stream )函数的使用,会将内存中的数据写入到文件中去,

void filetest() 
{
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt","w");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//顺序写入文件,将字符写到pf关联的文件中
	fputc('a',pf);
	fputc('b', pf);
	fputc('c', pf);
	fputc('d', pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
}

当程序成功执行起来的时候,可以看到文本文件已经存放了abcd四个字符
在这里插入图片描述
那么有没有读者会比较好奇fputc是怎么将字符写入到文件中的?
其实pf这个信息区里面存放了一个指针用来记录存取字符的位置,当存入字符后,指针会往后偏移,这样子一点一点地就将数据存放完了,

流的概念

fputc他是适用于所有输出流的(文件 / 标准输出),在我们c语言中有一个流的概念,流相当于水流,他是一种高度抽象的概念,而一些外部设备比如:文件、屏幕、网络、光盘、软盘,我可以从一些外部设备中读写一些数据,而不同外部设备的读写方式肯定不一样,而作为程序员不需要关心怎么向外部设备读写数据,就在这些外部设备该需要采用如何读写的方式这个问题交给谁的时候,c语言封装了流,程序员只需要关心如何往流里面输入数据,并不需要关心数据给谁,数据给谁都是流来考虑
在这里插入图片描述

  • c语言的程序只要一运行起来就会默认打开3个流:
  • 标准输出流 - stdout
  • 标准输入流 - stdin
  • 标准错误流 - stderr
  • 这3个流的类型都是FILE类型

fputc适用于所有输出流,这里再举例标准输出流(屏幕)

void fpunc() 
{	//将字符顺序输出到屏幕
	fputc('a',stdout);
	fputc('b', stdout);
	fputc('c', stdout);
	fputc('d', stdout);
}

打印结果
在这里插入图片描述

字符输入函数fgetc

// 适用于所有输入流
int fgetc( FILE *stream );
// stream:FILE结构的指针

fgetc适用于所有输入流,这个函数功能是可以从一个流里面读取数据然后返回,当然这里是从文件流里面读取数据

void filetest() 
{
	
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt","r");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//从文件流中读取数据返回存放到变量ch中
	int ch = fgetc(pf);
	printf("%c",ch);

	ch = fgetc(pf);
	printf("%c", ch);

	ch = fgetc(pf);
	printf("%c", ch);

	ch = fgetc(pf);
	printf("%c", ch);
	//关闭文件
	fclose(pf);
	pf = NULL;
}

打印结果
在这里插入图片描述
其实pf这个信息区里面存放了一个指针用来记录存取字符的位置,当取出一个字符后,指针会往后偏移,这样子一点一点地就将数据依次取出来了

fgetc函数读取标准输入流(键盘)

void pfunc() 
{
	//从标准输入流读取字符存放到变量ch中
	int ch = fgetc(stdin);
	printf("%c\n",ch);

	 ch = fgetc(stdin);
	printf("%c\n", ch);

	 ch = fgetc(stdin);
	printf("%c\n", ch);

	 ch = fgetc(stdin);
	printf("%c\n", ch);
}

打印结果
在这里插入图片描述

总结:

  • 1、fgetc()是适用于所有的输入流 ,它可以用于读入所有输入流
  • 2、fputc()函数是适用于所有的输入流,它可以用于输出所有输出流

文本行输出函数 fputs

功能:将一个字符串输出到文件中
函数原型:

int fputs( const char *string, FILE *stream );
string : 想要输出的字符串
stream : FILE*类型的结构体指针 
void filetest() 
{
	
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt","w");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//写文件,将字符串写到pf关联的文件中
	fputs("男人喜欢吃米饭\n",pf);
	fputs("女人喜欢吃面食\n", pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
}

程序运行后的结果:
可以看到字符串成功地写入进了文件中去,这个函数注意的地方是如果你想让他换行就必须再字符串中加入‘\n’
在这里插入图片描述

文本行输入函数 fgets

这个函数的功能是从流中读取一个字符存放到一个字符指针中去,前提是字符指针指向的空间够大

char *fgets( char *string, int n, FILE *stream );
string : 数据存储位置
n :可读的最大字符数
stream :FILE结构的指针

文本行输入函数 fgets的使用以及注意事项

void filetest() 
{
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt","r");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//写文件,将字符串写到pf关联的文件中
	char arr[20] = { 0 };
	fgets(arr,5,pf);
	printf("%s", arr);
	fgets( arr,5,pf);

	printf("%s",arr);
	//关闭文件
	fclose(pf);
	pf = NULL;
}

从程序运行的结果不妨猜测一下为什么,结果会是这样子
在这里插入图片描述
其实fgets这个函数的参数n可读的最大字符数虽然是5,以为可以读5个字符,实际上只能读到n - 1个字符,也就是4个字符,并且末尾会补‘\0’保证有5个字符

那么如果是读取n = 20个他会全部读完吗?

void filetest() 
{
	
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt","r");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//已将读取的长度改为20
	char arr[20] = { 0 };
	fgets(arr,20,pf);
	printf("%s\n", arr);
	fgets( arr,20,pf);

	printf("%s\n",arr);
	//关闭文件
	fclose(pf);
	pf = NULL;
}

从打印的结果可以看出不管n指定有多大,fgets只会读取一行数据,并不会为了填充这20个大小的目标而去读下一行字符串,
在这里插入图片描述

格式化输出函数 fprintf

函数功能:以指定的格式将数据输出到流里面去
fprintf函数的原型:

int fprintf( FILE *stream, const char *format [, argument ]...);
stream:FILE结构的指针

printf函数的原型和fprintf函数的原型对比:
在这里插入图片描述
我们可以看出fprintf和printf的函数原型好像基本是一样的,那么它们的使用是不是也基本一样呢?下面来试试看

void filetest() 
{
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt","w");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//写文件,将数据写到pf关联的文件中
	int arr[10] = { 0 };
	for (int i = 0; i < 10; i++) 
	{
		fprintf(pf, "%d\n", arr[i]++);
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
}

程序执行完的结果:
在这里插入图片描述

我们可以看出好像fprintf和printf的使用上没太大的区别,只是一个是输出到屏幕上去的,而另外一个是输出到文件上去的

格式化输入函数 fscanf

函数功能:以指定的格式读取出流里面的数据放到内存中去
fscanf函数的原型:

int fscanf( FILE *stream, const char *format [, argument ]... );

可以看出fscanf和scanf函数原型上其实也差不多,在这里就直接对标了哈哈哈
在这里插入图片描述

代码:

void filetest() 
{
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt","r");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//读文件,从文件流中拿去数据存放到a和b变量中
	int a = 0, b = 0;
	fscanf(pf,"%d %d", &a, &b);
	printf("%d %d", a, b);
	//关闭文件
	fclose(pf);
	pf = NULL;
}

原本就存放在文件中的数据:
在这里插入图片描述

打印的结果:
在这里插入图片描述

二进制输出 fwrite

功能:以二进制的方式将程序中的数据输出到文件中

size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );

buffer:指向要写入数据的指针	
size:元素大小(以字节为单位)
count:要写的元素的最大个数
stream :FILE结构的指针

代码

struct s 
{
	int a;
	int b;
	char name[20];
};
void filetest() 
{
	//"wb":为了输出数据,打开一个二进制文件
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt", "wb");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//写文件,将数据写到pf关联的文件中
	struct s s = { 0,40 ,"zhangsan"};
	fwrite(&s,sizeof(struct s),1,pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
}

程序运行的实际效果,这里我们可以看到这样zhangsan的拼音是我们看的懂得,因为字符串本身就是以文本形式存放,而剩下得数据我们就看不懂了,因为是以二进制的形式写进文件里去的,二进制的形式写进去的跟以文本写进去的会有很大区别
在这里插入图片描述

既然以看不懂二进制的表示方式,那我们就用程序将这个文本文件的信息读取出来,“rb”(只读) 为了输入数据,打开一个二进制文件,二进制输入,fread登场

二进制输入 fread

功能:从一个流里面读取count个大小为size(单位字节)的数据存放到buffer里去

size_t fread( void *buffer, size_t size, size_t count, FILE *stream );

buffer:数据存储位置
size:元素大小(以字节为单位)
count:要读取的最大的元素个数
stream :FILE结构的指针

在这里插入图片描述

Fread返回实际读取的完整项的数量,如果发生错误或在达到count之前遇到文件结束,则可能小于count。使用feof或ferror函数来区分读错误和文件结束条件。如果size或count为0,fread返回0并且缓冲区内容不变。

在这里插入图片描述

对标一下我们可以发现,其实它们之间的差别就是一个加了const和没加const的区别,可实际上却并不是这点差别

代码:

void filetest() 
{
	struct s s = { 0 };
	//"rb" 为了输入数据,打开一个二进制文件
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt", "rb");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//读文件,打开一个二进制的文件
	fread(&s,sizeof(struct s),1,pf);
	printf("%d %d %s",s.a,s.b,s.name);
	//关闭文件
	fclose(pf);
	pf = NULL;
}

我们最先开始存放进去的是这个数据,显示在文本中后看到的效果又是这样
在这里插入图片描述
程序运行:
在这里插入图片描述

从程序运行的结果来看,确确实实地是将文件中的数据以二进制的方式输入到内存中去了,就像是给结构体重新赋值了一样

对比一组函数:

scanf/fscanf/sscanf
printf/fprintf/sprintf

结论:
在这里插入图片描述

sprintf

在这里插入图片描述
函数功能:是将一个格式化的数据转换成一个字符串
代码:

void pfunc() 
{
	char arr[20] = { 0 };
	struct s s = {10,100,"zhangsan"};

	//将一个格式化的数据转换成字符串,在存放到数组中去
	sprintf(arr, "%d %d %s", s.a, s.b, s.name);
	//输出数组中的内容到屏幕上去
	printf("%s\n",arr);
}

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

sscanf

在这里插入图片描述
函数功能:是从arr的字符串中提取出一个格式化的数据

代码:

void pfunc() 
{
	char arr[20] = { 0 };
	struct s s = {10,100,"zhangsan"};
	struct s tmp = { 0 };
	//将一个格式化的数据转换成字符串,存放到arr中
	sprintf(arr, "%d %d %s", s.a, s.b, s.name);

	//将arr字符串的中提取出一个格式化的数据,存放到tmp结构体中
	sscanf(arr,"%d %d %s",&tmp.a, &tmp.b, tmp.name );
	//打印结构体的数据
	printf("%d %d %s\n",tmp.a,tmp.b,tmp.name);
}

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

文件的随机读写

在这里插入图片描述

fseek

  • 将文件指针移动到指定位置。
  • 根据文件指针的位置和偏移量来定位文件指针
int fseek( FILE *stream, long offset, int origin );

stream:FILE结构的指针
offset:从原点开始的字节数
origin:初始位置

SEEK_CUR

Current position of file pointer – 文件指针的当前位置

SEEK_END

End of file — 文件终点

SEEK_SET

Beginning of file – 文件起始位置

如果文件指针指向的是中间位置c的话,那么从c的位置都是从0开始递增d字符是偏移量为1,e是文件指针的偏移量为2,e是文件指针的偏移量3,(右边为正数),而他的左边b是文件指针的偏移量为-1的位置,a是文件指针偏移量为-2的位置
在这里插入图片描述
如果文件指针指向的是起始位置a的地方,那么从a的位置处开始往后偏移都是正数
在这里插入图片描述
fseek 的使用代码

void filetest() 
{
	
	//"rb" 为了输入数据,打开一个二进制文件
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt", "r");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//文件打开成功,从文件指针的起始位置往后偏移2个字节数,读完一个字符文件指针还会向后偏移一字节
	fseek(pf,2,SEEK_SET);
	int ch = fgetc(pf);
	printf("%c\n", ch);

	//如果想往回再读b,可以从当前位置向前偏移-2个字节
	fseek(pf,-2,SEEK_CUR);
	ch = fgetc(pf);
	printf("%c\n", ch);
	//关闭文件
	fclose(pf);
	pf = NULL;
}

从运行结果结果可以,fseek 确实是具有移动文件指针的能力的,配合着fgetc函数使用,基本就是指哪打哪,即使是操作文件也搓搓有余
在这里插入图片描述

ftell

计算文件指针相对于起始位置的偏移量函数

long ftell( FILE *stream );
函数功能:获取文件指针的当前位置。

在这里插入图片描述

rewind

让文件指针的位置回到文件的起始位置

void rewind ( FILE * stream );

上个例子我们已经看到了ftell他就像一个追踪器一样跟着文件指针,文件指针走哪他也走哪,那么接下来再介绍一个函数,比较霸气的函数rewind,妈妈让你回家吃饭你就回家吃饭文件指针别乱跑
在这里插入图片描述
从程序的运行结果可以看出文件指针还是一个好孩子,已经回到了偏移量为0的起始位置,哈哈

文本文件和二进制文件

  • 根据数据的组织形式,数据文件被称为文本文件或者二进制文件。

  • 数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。 .

  • 如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。

  • 一些二进制文件比如exe,一些数据文件比如txt

总结:

  • 1、二进制文件就是在内存中以二进制形式存储的数据不加转换地输出到外存(文件)
  • 2、文本文件要求在外存上以ASCII码的形式存储,则需要在存储前转换,转换之后的数据输出到外存上(文件),

关于二进制文件和文本文件的区别

  • 一个数据在内存中是怎么存储的呢? 字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
  • 如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节(VS2013测试)

测试代码:

#include <stdio.h>
int main()
{
 int a = 10000;
 FILE* pf = fopen("test.txt", "wb");
 fwrite(&a, 4, 1, pf);//二进制的形式将数据写入文件中
 fclose(pf);
 pf = NULL;
 return 0; }

那么10000是怎么以二进制的形式存放到内存中去的呢
10000的二进制序列:00000000 00000000 00100111 00010000
在这里插入图片描述
10000的二进制序列转化为16进制是 00 00 27 10
在这里插入图片描述

当我们将程序运行后把这个二进制文件一打开确实看见了10000对应的16进制,由于在vs2017是小端存储所以低地址处存放低地址处数据,高地址处存放高地址处数据,所以看到的是反过来的
在这里插入图片描述

文件结束的判定

在这里插入图片描述

  • 文本文件读取是否结束,判断返回值是否为EOF (fgetc),或者NULL(fgets)
  • 例如: fgetc判断是否为EOF.
  • fgets判断返回值是否为NULL.

2.二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。 例如: fread / fscanf 判断返回值是否小于实际要读的个数

被错误使用的 feof

int feof( FILE *stream );
  • 牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。
  • 而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束

比如fgets是读到文件末尾了才结束,那么如果中途就停止读文件了,就可以使用feof 函数来判断他是遇到文件末尾了,还是中途读取失败

来看feof的使用

 int c; // 注意:int,非char,要求处理EOF
    FILE* fp = fopen("test.txt", "r");
    if(!fp) {
        perror("File opening failed");
        return EXIT_FAILURE;
   }
 //fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
    while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环
   { 
       putchar(c);
   }
 //判断是什么原因结束的
 //如果文件读取失败就会返回错误信息
    if (ferror(fp))
        puts("I/O error when reading");
 //feof判断文件是否成功读到末尾
    else if (feof(fp))
        puts("End of file reached successfully");
    fclose(fp);

总结:

feof的用途:是文件读取结束了,判断是不是遇到文件末尾而结束的
ferror的用途:文件读取结束了,判断是不是遇到错误后读取结束

文件缓冲区

  • ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。

  • 从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。

  • 如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。

  • 缓冲区的大小根据C编译系统决定的。
    在这里插入图片描述

验证缓冲区的存在

void pfunc() 
{
	FILE* pf = fopen("test.txt", "w");
	if (!pf)
	{
		perror("fopen:");
		exit(-1);
	}
	fputs("abcdef",pf);//先将数据放在输出缓冲区
	printf("已经休眠10秒钟 -- 打开test.txt文件,发现文件没有内容\n");
	Sleep(10000);
	printf("刷新缓冲区\n");
	//函数刷新缓冲区时,才将缓冲区的数据写到文件中去,而fflush函数就有刷新缓冲区的作用
	fflush(pf);
	printf("再休眠10秒钟 -- 打开test.txt文件,发现文件有内容\n");
	//fclose函数再关闭文件时也会刷新缓冲区
	fclose(pf);
	pf = NULL;
}

请添加图片描述
fputs(“abcdef”,pf);代码只是将数据输出到了缓冲区中,等待10秒后当缓冲区被刷新的时候,才会将缓冲区的数据存放到文件中

这里还可以得到结论:
因为有缓冲区的存在,c语言操作文件的时候,需要刷新缓冲区或者在文件结束的时候关闭文件,如果不做,可能会无法保存


点击全文阅读


本文链接:http://zhangshiyu.com/post/34623.html

文件  指针  数据  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1