C语言文件操作

本文详细介绍了C语言中的文件操作,包括文件的打开与关闭、文件指针、顺序读写、二进制文件读写、随机读写以及文件结束判定。重点讲解了fopen、fclose、fgetc、fputc、fgets、fputs、fprintf、fscanf、fwrite、fread等函数的使用,并探讨了文件缓冲区的概念和管理。通过对文本文件和二进制文件的实例分析,深入理解了C语言文件操作的各个方面。
摘要由CSDN通过智能技术生成


本章我们来学习一下c语言的文件操作。
本章主要内容:
  什么是文件
  文件名
  文件类型
  文件缓冲区
  文件指针
  文件的打开和关闭
  文件的顺序读写
  文件的随机读写
  文件结束的判定

什么是文件?

文件是保存在持久化存储设备(硬盘、U盘、光盘…)上的一段数据。

文件分类

在程序设计中,按功能分文件有两种:程序文件数据文件

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

程序文件

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

数据文件

文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。

比如,现在有源文件test.c,头文件test.h,编译链接后生成可执行程序test.exe,test.c中操作文件data.txt,对data.txt进行读写操作,这里test.c、test.h、test.exe是程序文件,test.txt是数据文件。

在这里插入图片描述

在本章,我们讨论的都是数据文件

在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。比如,我们之前使用printf函数将数据打印到屏幕,使用scanf函数从键盘获取数据。

在这里插入图片描述

其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上的文件。

文件名

一个文件要有一个唯一的文件标识,以便用户识别和引用。

文件名包含3部分:文件路径+文件名主干+文件后缀
例如: c:\code\test.txt

为了方便起见,文件标识常被称为文件名

文件的打开和关闭

文件指针

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

每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名FILE。

在VS2008编译环境中,stdio.h头文件中声明FILE:

struct _iobuf {
	char *_ptr;
	int _cnt;
	char *_base;
	int _flag;
	int _file;
	int _charbuf;
	int _bufsiz;
	char *_tmpfname;
	};
typedef struct _iobuf FILE;

不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。比如在VS2019编译环境中,做了进一步封装,FILE声明如下:

typedef struct _iobuf
    {
        void* _Placeholder;
} FILE;

每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构类型的变量,并填充其中的信息,然后一般都是通过一个FILE类型的指针来维护这个FILE结构的变量,这样使用起来更加方便。

定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件,我们不必关心内部细节是如何实现的。

在这里插入图片描述

在这里插入图片描述

文件的打开和关闭

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。

操作文件的步骤:
1.打开文件
2.读/写文件
3.关闭文件

在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。

ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。

打开文件

FILE *fopen( const char *filename, const char *mode );
参数:
filename:文件名
mode:打开方式

函数功能:打开一个文件。

打开成功时,返回一个指向已打开文件的指针。返回空指针NULL表示打开失败。

有如下打开方式:
在这里插入图片描述
关闭文件

int fclose( FILE *stream );
参数:
stream:要关闭的文件结构体指针

函数功能:关闭一个流。

如果流被成功关闭,fclose返回0,返回EOF表示错误。

fclose不会把stream置为NULL,关闭文件后,我们应该把stream置为NULL。

读文件演示:

#include <stdio.h>
int main()
{
	//1.打开文件
	FILE* pf = fopen("C:\\Users\\win\\Desktop\\data.txt","r");

	if (NULL == pf)
	{
		//打开文件失败
		perror("fopen");
		return - 1;
	}

	//2.读写文件

	//3.关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

因为现在还没有data.txt文件,所以打印错误信息。
在这里插入图片描述
绝对路径:“C:\Users\win\Desktop\data.txt”
相对路径:“data.txt” 在当前路径下查找

文件读写

在介绍文件读写之前,我们先要弄清楚几个概念,什么是输入输出?什么是外设?什么是标准输入流、标准输出流、标准错误流?

首先我们来了解一下什么叫外部设备

外设:外部设备是指连在计算机主机以外的设备,它一般分为输入设备、输出设备和外存储器,外部设备是计算机系统中的重要组成部分。

常见的输入设备:键盘、鼠标等;
常见的输出设备:屏幕、打印机等;
常见的外存储器:硬盘存储器、移动存储器等。

文件存储在外存储器上。

IO
要弄清什么是标准输入输出。首先需要弄懂什么是IO。
IO 的 I 是 Input 的意思,O 是 output 的意思,意味着输入和输出。

更确切的含义是:
I:从外部设备(输入设备)输入到内存
O:从内存输出到外部设备(输出设备)

所谓的输入输出,是我们站在内存的角度来看,进入内存的,我们称之为I,从内存输出的,我们称之为O。

键盘是标准输入,屏幕是标准输出。

**由于外部设备太多,所以在外部设备的基础上抽象出 — “流”的概念。**我们只要把数据放到“流”中即可。

标准流

我们之前学习的printf函数、scanf函数、getchar函数、putchar函数、gets函数、puts函数都使用了流。

scanf函数、getchar函数、gets函数都是指从标准输入流(键盘)获取数据;
printf函数、putchar函数、puts函数都是将数据打印到标准输出流(屏幕)。

我们之所以能直接使用流的输入输出操作,是因为c语言的程序只要运行起来,就默认打开3个标准流,即

  • 标准输出流 - stdout
  • 标准输入流 - stdin
  • 标准错误流 - stderr

这3个标准流的类型都是FILE*类型指针

  • 标准输出流一般是指屏幕
  • 标准输入流一般是指键盘
  • 标准错误流一般是指屏幕

了解了这些,我们来学习文件读写操作。

文件的顺序读写

在这里插入图片描述
读文件是从文件中读取数据放到内存中
写文件是将内存中的数据写到文件中

以下是关于文件读写的相关库函数。
在这里插入图片描述
tips:与文件相关的读写操作库函数,都是以f开始的。

所谓顺序读写,就是从头到尾连续读写。

文本文件读写

文件读写相关函数使用

fgetc函数

int fgetc( FILE *stream );

函数功能:从输入流中读取一个字符
参数:    stream是输入流

int main()
{
	FILE* pf = fopen("myfile.txt","r");
	if (pf == NULL)
	{
		perror("fopen");
		return -1;
	}
	char ch = fgetc(pf);
	printf("%c",ch);
	fclose(pf);
	pf = NULL;
	return 0;
}
int main()
{
	//getchar函数从标准输入流获取一个字符
	char ch = getchar();

	//向标准输出流输出字符ch
	printf("%c\n",ch);

	//向标准输出流输出字符ch
	fprintf(stdout,"%c\n",ch);

	//从标准输入流获取一个字符
	ch = fgetc(stdin);

	return 0;
}

fputc函数

int fputc( int c, FILE *stream );

函数功能:向输出流中写入一个字符

参数:c是要写入的字符
   stream是要写入的流

int main()
{
	FILE* pf = fopen("myfile.txt","w");
	if (pf == NULL)
	{
		perror("fopen");
		return -1;
	}

	//写文件
	fputc('b',pf);
	fputc('y',pf);
	fputc('t',pf);
	fputc('e',pf);

	fclose(pf);
	pf = NULL;
	return 0;
}

```c
int main()
{
	//向标准输出流输出一个字符
	putchar('h');

	putchar('\n');
	
	//向标准输出流输出一个字符'h'
	fputc('h',stdout);

	return 0;
}

fgets函数

char *fgets( char *string, int n, FILE *stream );

函数功能:从输入流中读取字符串,最多读取n个字符
(实际读取n-1个字符,最后一个位置放’\0’,'\0’是字符串结束标志)
参数:  stream是输入流;string是从输入流中读取的字符串;n是从输入流读取的最大字符个数。

注意:fgets函数最多读一行,该函数读满一行或者读取n-1个字符后就结束,不再继续向下读(如果要读取的n个字节大于一行字符个数,那么就读一行结束),最多读一行,不会跨行读。

int main()
{
	FILE* pf = fopen("test.txt","r");
	if (NULL == pf)
	{
		perror("fopen");
		return -1;
	}

	//读一行数据
	char arr[20] = {0};

	fgets(arr,30,pf);
	printf("%s\n",arr);
	
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

fputs函数

int fputs( const char *string, FILE *stream );

函数功能:向输出流中写入字符串,如果写入成功返回一个非负数,如果出错返回EOF。

对于fputs,如果想达到换行的效果,那么字符串中要有换行符。

int main()
{
	FILE* pf = fopen("test.txt","w");
	if (NULL == pf)
	{
		perror("fopen");
		return -1;
	}

	//写文件
	//写一行数据
	fputs("hello world\n",pf);
	fputs("hello world\n",pf);

	//写多行数据
	fputs("hello C\nhello c++\n",pf);

	return 0;
}

在这里插入图片描述
fprintf函数

int fprintf( FILE *stream, const char *format [, argument ]…);

函数功能:格式化输入函数,按照某种格式将数据写入输出流中

struct S
{
	int n;
	double d;
};
int main()
{
	struct S s = {100,3.14};
	FILE* pf = fopen("test.txt","w");

	if (NULL == pf)
	{
		perror("fopen");
		return -1;
	}
	//写文件
	fprintf(pf,"%d %lf",s.n,s.d);
	fclose(pf);
	pf = NULL;

	return 0;
}

在这里插入图片描述

fscanf函数

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

函数功能:格式化输入函数,按照某种格式把数据从输入流中读出来

struct S
{
	int n;
	double d;
};

int main()
{
	struct S s = { 0,0 };
	FILE* pf = fopen("test.txt", "r");

	if (NULL == pf)
	{
		perror("fopen");
		return -1;
	}
	//读文件
	fscanf(pf, "%d %lf", &(s.n), &(s.d));
	//将输入流(文件)中的数据以格式化的形式放到变量s.n、s.d中
	printf("%d %lf",s.n,s.d);
	fclose(pf);

	pf = NULL;

	return 0;
}

在这里插入图片描述

perror函数

void perror( const char *string );

perror(s) 用来将上一个函数发生错误的原因输出到标准设备(stderr),也就是输出到标准错误流(屏幕上)。

参数 s 所指的字符串会先打印出来,后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。(没有错误时,是 No error,这也是perror函数的一个缺点,就是perror函数必然会打印,不管有没有error)

在库函数中有个errno变量,每个errno值对应着以字符串表示的错误类型。当你调用"某些"函数出错时,该函数已经重新设置了errno的值。perror函数只是将你输入的一些信息和errno所对应的错误一起输出。

#include <stdio.h>
int main ()
{
	FILE * pFile;
	pFile = fopen ("myfile.txt","w");
	if (pFile!=NULL)
	{
		fputs ("fopen example",pFile);
		fclose (pFile);
		pf = NULL;
	}
	return 0;
}

printf()其实就是向stdout中输出,等同于fprintf(stdout,“****”);

scanf(“%s”,p)其实就是从stdin中获取,等同于fscanf(stdin,“%s”,p);

perror()其实就是向stderr中输出,相当于fprintf(stderr,“***”),

getchar()其实就是从stdin中获取一个字符,相当于fgetc(stdout);

putchar()其实就是向stdout中输出,相当于fputc(“***”,stdout);

对比三组函数

scanf:从标准输入(键盘)读取格式化的数据
printf:打印格式化的数据到标准输出(屏幕)上

fscanf:从所有的输入流中读取格式化的数据(fscanf包含了scanf的功能)
fprintf:把格式化的数据输出到所有输出流上(fprintf包含了printf的功能)

sscanf函数和sprintf函数和文件没有关系。

sscanf从一个字符串中读取格式化的数据(把字符串转化成格式化的数据)

int sscanf( const char *buffer, 
	const char *format [, argument ] ... );

示例:

#include <stdio.h>
struct S
{
	int a;
	double b;
};
int main()
{
	char arr[] = "helloworld";
	struct S s = {0};
	
	//从字符串中读取格式化的数据
	sscanf(arr,"%s",s.c);
	printf("%s\n",s.c);
	return 0;
}

sprintf:把格式化的数据写入字符串

int sprintf( char *buffer, const char *format [, argument] … );

示例:

#include <stdio.h>
struct S
{
	int a;
	double b;
};

int main()
{
	struct S s = {100,3.14};

	char arr[100] = {0};
	//把格式化的数据写入字符串中
	sprintf(arr,"%d %lf",s.a,s.b);

	printf("%s\n",arr);
	return 0;
}

示例:

int main()
{
	char  tokenstring[] = "15 12 14...";
	char  s[81];
	char  c;
	int   i;
	float fp;

	/* Input various data from tokenstring: */
	sscanf(tokenstring, "%s", s);
	sscanf(tokenstring, "%c", &c);
	sscanf(tokenstring, "%d", &i);
	sscanf(tokenstring, "%f", &fp);

	/* Output the data read */
	printf("String    = %s\n", s);
	printf("Character = %c\n", c);
	printf("Integer:  = %d\n", i);
	printf("Real:     = %f\n", fp);

	return 0;
}

结果为:
在这里插入图片描述
示例:

typedef struct S
{
	char c[12];
	int n;
}S;

int main()
{
	S s = {0};
	char arr[30] = "hello 2021";
	sscanf(arr,"%s %d",s.c,&s.n);

	printf("%s %d\n",s.c,s.n);
	strcpy(s.c,"hello world");
	s.n = 1992;
	sprintf(arr,"%s %d",s.c,s.n);

	printf("%s\n",arr);
	return 0;
}

sscanf函数和scanf一样,遇到空格就停止不再继续读取。

二进制文件读写

前面学习的都是文本文件的读写操作,数字100,我们是把它看成1,0,0三个字符,写入文件中的。

那么对于二进制文件,数字100,我们把它的二进制表示形式存储到文件中。

相关函数使用

fwrite函数

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

函数功能:把数据(二进制形式)写入输出流中

参数:
buffer是要写入的内存块的起始地址
size是要写入的每个元素的大小
count是要写入的元素的个数
stream是输出流

返回值:
成功写入的元素个数。如果发生错误,返回值可能小于count。此外,如果发生错误,则无法确定文件位置。

struct S
{
	int n;
	char name[10];
};

int main()
{
	struct S s = { 10,"zs"};
	FILE* pf = fopen("test.txt", "wb");

	if (NULL == pf)
	{
		perror("fopen");
		return -1;
	}
	//二进制写文件
	fwrite(&s,sizeof(s),1,pf);

	pf = NULL;

	return 0;
}

在这里插入图片描述

fread函数

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

函数功能:从输入流中读数据(以二进制的形式读)

参数
size是要读取的每个元素的大小
count是要读取的元素的个数
buffer是读取出来的数据要存放的地方,是void*类型,因为并不知道要读取出来的数据是什么类型的。
stream是输入流

返回值
读取的完整元素的个数。

如果读取发生错误,或者如果在读取count个元素之前遇到文件的末尾,则返回值可能小于count。

使用feof或ferror函数来区分读错误和文件结束条件。如果size或count为0,则fread返回0,且缓冲区内容不变。

我们以二进制形式写入文件的,就要以二进制形式读出来。

struct S
{
	int n;
	char name[10];
};

int main()
{
	struct S s = { 0,0 };
	FILE* pf = fopen("test.txt", "rb");

	if (NULL == pf)
	{
		perror("fopen");
		return -1;
	}
	//二进制读文件
	fread(&s, sizeof(s), 1, pf);
	
	//从输入流pf中读取数据放到s中
	printf("%d %s",s.n,s.name);
	fclose(pf);
	pf = NULL;

	return 0;
}

文件的随机读写

相关函数使用

fseek函数
在这里插入图片描述
函数功能:根据文件指针的位置和偏移量来定位文件指针
参数:  stream是FILE类型指针
     offset是从origin位置开的偏移量(可整可负)
     origin是指针初始位置

fseek函数将FILE类型指针stream移动到一个新的位置处,stream的初始位置为origin,新位置与初始位置相差offset个字节,移动完成后,后续的文件读/写操作都是从新的位置开始。

参数origin的可用值如下:

  • SEEK_CUR:
    Current position of file pointer 文件指针当前所在位置

  • SEEK_END:
    End of file 文件尾

  • SEEK_SET:
    Beginning of file 文件开始位置

#include <stdio.h>
//文件的随机读写
int main()
{
	//1.打开文件
	FILE* fp = fopen("data.txt","r");

	if (fp == NULL)
	{
		perror("fopen");
		return;
	}
	//假设文件中的内容为:abcdefghijklmn
	
	//2.读文件
	//随机读写

	//(1).从头开始读
	//int c1 = fgetc(fp);
	//printf("%c\n",c1);


	//(2).从起始位置向后偏移2个字节处,开始读取
	fseek(fp,2,SEEK_SET);
	 
	int c2 = fgetc(fp);
	printf("%c\n", c2);
	//此时文件指针指向距离起始位置3个字节偏移量的位置

	//(3).从当前指针位置向前偏移2个字节处,开始读取
	fseek(fp, -2, SEEK_CUR);

	int c3 = fgetc(fp);
	printf("%c\n", c3);

	//3.关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
}

ftell函数

在这里插入图片描述
函数功能:返回文件指针相对于起始位置的偏移量

如果是以追加模式打开文件,有以下几个注意点

  • 如果以追加模式打开文件,如果还没有任何读写操作,那么文件指针是在起始位置;
int main()
{
	//1.打开文件
	FILE* fp = fopen("data.txt", "ab+");

	if (fp == NULL)
	{
		perror("fopen");
		return;
	}
	//假设文件中的内容为:abcdefg

	//2.写文件	
	
	//尚未进行任何读写操作,指针位于文件起始位置
	long offset = ftell(fp);
	printf("%ld\n", offset);//0

	//写入文件
	char str[] = "hello world";
	fwrite(str, sizeof(str), 1, fp);

	//文件指针到文件末尾
	offset = ftell(fp);
	printf("%ld\n", offset);//19

	//3.关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
}
  • 当前文件的位置是由最后一次读写操作决定的,而不是由下一次写入发生的位置决定的。

例如,如果为了追加而打开一个文件,并且最后一个操作是读操作,那么文件位置就是下一个读操作开始的位置,而不是下一个写操作开始的位置,那么在进行任何写操作之前,应将文件位置将移动到文件的末尾。

int main()
{
	//1.以追加模式打开文件
	FILE* fp = fopen("data.txt", "ab+");

	if (fp == NULL)
	{
		perror("fopen");
		return;
	}
	//假设文件中的内容为:abcdefghijklmn

	//2.读写文件

	//此时还没有任何读写操作,文件指针位于起始位置
	long offset = ftell(fp);
	printf("%ld\n", offset);//0

	//读文件
	char arr[5] = {0};
	fread(arr,5,1,fp);
	printf("%s\n",arr);

	//读了5个字符,此时文件指针在距离起始位置5个字节位置
	offset =  ftell(fp);
	printf("%ld\n", offset);//5

	//在进行写操作之前,将文件指针移动到文件末尾
	fseek(fp,0,SEEK_END);
	//文件指针到达末尾
	offset = ftell(fp);
	printf("%ld\n", offset);//14

	//写文件
	char str[] = "hello world";
	fwrite(str,sizeof(str),1,fp);
	
	//文件指针在末尾位置
	printf("%ld\n",ftell(fp));

	char str1[] = "hello 2021";
	fwrite(str1, sizeof(str1), 1, fp);

	printf("%ld\n", ftell(fp));

	//3.关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
}

文件分类

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

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

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

字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。

比如,我们以整数10000为例,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(把每一位看成一个字符,一个字符占一个字节);而如果以二进制形式输
出,则在磁盘上只占4个字节(VS2019编译器下)。

在这里插入图片描述
从图中可以看出,整数10000如果以文本文件形式存储,那么文件中我们看到的应该是10000(5个字符);

如果以二进制形式存储,文件中存储的应该是整数10000在内存中的形式。

以二进制形式存储1000,代码如下:

int main()
{
	FILE* fp = fopen("data.txt","wb");
	//wb:以写方式打开二进制文件
	if (fp == NULL)
	{
		perror("fopen");
		return -1;
	}
	int a = 10000;
	//以二进制形式写
	fwrite(&a,sizeof(int),1,fp);
	fclose(fp);
	fp = NULL;
	return 0;
}

我们调试代码,10000在内存中的存储形式是 10 27 00 00
在这里插入图片描述

程序运行结束后,我们右击data.txt文件,选择以二进制编译器方式打开,如下
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
10 27 00 00 正好是整数10000在内存中的存储形式(当前pc是以小端字节序存储)。

二进制文件:在内存中是大端,文件存储也是大端,内存中是小端,文件中存储的也是小端。二进制文件中存储的内容和内存中形式保持一致。

以文本文件形式存储10000,代码如下:

int main()
{
	FILE* fp = fopen("data.txt", "w");
	//w模式:以写方式打开文本文件
	if (fp == NULL)
	{
		perror("fopen");
		return -1;
	}

	//将10000以文本文件形式写文件
	fprintf(fp,"%d",10000);
	
	//fputs("10000", fp);
	
	/*char str[] = {'1','0','0','0','0'};

	int i = 0;
	for (i = 0; i < sizeof(str) / sizeof(str[0]); i++)
	{
		fputc(str[i],fp);
	}*/


	fclose(fp);
	fp = NULL;
	return 0;
}

运行程序,打开data.txt文件
在这里插入图片描述

文件结束判定

使用fgetc读取文件,可以通过判断fgetc的返回值是否是EOF来判定文件是否读取结束,而文件结束的原因可以是文件正常读取结束,也可能是遇到错误。

使用fgets返回字符串指针或者NULL.如果返回NULL,表示文件正常读取结束 或者 遇到错误。

被错误使用的 feof

牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。

feof函数的真正作用是:当文件读取结束的时候,判断结束的原因。是因为读取失败结束,还是因为遇到文件尾结束?

在这里插入图片描述
函数功能是检测流上的文件结束符,如果文件结束,则返回非0值,否则返回0。
(即,文件结束:返回非0值;文件未结束:返回0值)。

注意:feof函数判断文件结束是通过读取函数fread/fscanf等返回的错误码来识别的,所以feof判断文件是否结束应该是在读取函数之后进行判断。比如,在while循环读取一个文件时,如果是在读取函数之前进行判断,则如果文件最后一行是空白行,可能会造成内存错误。

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

例如:
fgetc判断返回值是否为EOF.
fgets判断返回值是否为NULL.

EOF的值为-1.

例子:

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


int main(void)
{
	int c; // 注意:int,非char,要求处理EOF
	FILE* fp = fopen("test.txt", "r");
	if(!fp) 
	{
		perror("File opening failed");
		return -1;
	}
	
	//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
	while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环
	{
		putchar(c);
	}
	
	//走到这里说明fgetc函数已经读取结束
	//判断是什么原因结束的
	if (ferror(fp))
	{
		//发生错误而结束
		puts("I/O error when reading");
	}
	else if (feof(fp))
	{
		//是因为到文件末尾结束的
		puts("End of file reached successfully");
	}
			
	fclose(fp);
}

ferror函数

在这里插入图片描述
如果没有错误发生,ferror函数返回0,否则返回非0整数。

ferror和feof对比

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

ferror的用途:文件读取结束后,判断是不是遇到错误导致的结束。

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

例如:
fread判断返回值是否小于实际要读的个数。

enum { SIZE = 5 };
int main(void)
{
	double a[SIZE] = { 1.0,2.0,3.0,4.0,5.0 };
	double b = 0.0;
	size_t ret_code = 0;

	FILE* fp = fopen("test.bin", "wb"); // 必须用二进制模式
	
	if (fp == NULL)
	{
		perror("fopen");
		return -1;
	}

	//写文件
	fwrite(a, sizeof(*a), SIZE, fp); // 写 double 的数组

	fclose(fp);


	fp = fopen("test.bin", "rb");
	if (fp == NULL)
	{
		perror("fopen");
		return -1;
	}
	
	// 读 double 的数组
	while ((ret_code = fread(&b, sizeof(double), 1, fp)) >= 1)
	{
		printf("%lf\n", b);
	}

	//读文件结束,判断因为什么原因结束
	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;
}

文件缓冲区

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

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

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

缓冲区的大小根据C编译系统决定的。

在这里插入图片描述

刷新缓冲区条件

  1. 缓冲区被写满(linux下默认缓冲区的大小为1024字节);
  2. 程序执行结束或者文件对象被关闭;
  3. 行缓冲遇到换行;
  4. 主动刷新,程序中调用fflush()函数 (调用一次fflush函数,就刷新一次,int fflush( FILE *stream ); )。

缓冲区的大小

如果我们没有自己设置缓冲区的话,系统会默认为标准输入输出设置一个缓冲区,这个缓冲区的大小通常是512个字节的大小。

缓冲区大小由 stdio.h 头文件中的宏 BUFSIZ 定义,我们可以在stdio.h文件中查看该值的大小:
在这里插入图片描述

缓冲区的大小是可以改变的,可以学习库函数setbuf函数和setvbuf函数。

setbuf函数

在这里插入图片描述

#include <stdio.h>

void main(void)
{
    char buf[BUFSIZ] = {0};
    FILE* stream1, * stream2;

    if (((stream1 = fopen("data1", "a")) != NULL) &&
        ((stream2 = fopen("data2", "w")) != NULL))
    {
        setbuf(stream1, buf);
        printf("stream1 set to user-defined buffer at: %Fp\n", buf);

        setbuf(stream2, NULL);
        printf("stream2 buffering disabled\n");
        _fcloseall();
    }
}

setvbuf函数

在这里插入图片描述

int main()
{
    char buf[1024];
    FILE* stream1, * stream2;

    if (((stream1 = fopen("data1", "a")) != NULL) &&
        ((stream2 = fopen("data2", "w")) != NULL))
    {
        if (setvbuf(stream1, buf, _IOFBF, sizeof(buf)) != 0)
            printf("Incorrect type or size of buffer for stream1\n");
        else
            printf("'stream1' now has a buffer of 1024 bytes\n");
        if (setvbuf(stream2, NULL, _IONBF, 0) != 0)
            printf("Incorrect type or size of buffer for stream2\n");
        else
            printf("'stream2' now has no buffer\n");
        _fcloseall();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值