一篇内容带你学会文件操作

为什么使用文件

我们前面学习结构体时,数据是存放在内存中,当程序退出的时候,程序中的数据自然就不存在了,等下次运行程序的时候,数据又得重新录入,如果使用这样的程序就很难受。
我们在想既然是程序就应该把信息记录下来,只有我们自己选择删除数据的时候,数据才不复存在。
这就涉及到了数据持久化的问题,我们一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据
库等方式。
使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化。

什么是文件

磁盘上的文件是文件,文件一般分为两种:

1.数据文件

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

2.程序文件

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

文件的使用

文件一般分为三步:

  • 1.打开文件
  • 2.读/写
  • 3.关闭文件

在我们知道了文件的使用步骤后,我们需要直到怎么区操作文件。

文件指针

每一个使用的文件,都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(文件名、文件当前位置、文件状态等)。这些信息是保存在一个结构体变量FILE的,这个结构体变量是由系统声明的。每打开一个文件,系统就会根据该文件去创建FLIE类型的变量,我们要想对文件进行操作,就需要使用FILE*。

FILE* pf;//文件指针变量

定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件
在这里插入图片描述
在我们使用FILE*指针去访问、操作文件之前,我们需要先打开文件。

打开文件

在ANSIC中规定了打开文件需要fopen函数。
在这里插入图片描述

文件的打开方式:

在这里插入图片描述

#include<stdio.h>
int main()
{
	FILE* pf;
	pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("pf");
		return 1;
	}
	printf("打开成功");
	pf = NULL;
	return 0;
}

当我们这样写会不会报错呢?
在这里插入图片描述

在这里插入图片描述

为什么perror函数会提示我们No such file or directory?其实是博主当前文件目录下并没有data.txt这个文件,所以我们在使用fopen函数打开文件之前,一定要去查看我们当前目录下有没有你要打开的这个文件。如果没有这个文件,就不能使用r,而要使用w来创建。

#include<stdio.h>
int main()
{
	FILE* pf;
	pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("pf");
		return 1;
	}
	printf("创建并打开成功");
	fputc('a', pf);
	fputc('b', pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
但我们使用只读"w"后,可以看到当前目录下确实创建了一个data.txt文件,我们打开之后也确实是我们写进去的字符a,b。

关闭文件

关闭文件就显得很简单了,在我们打开一个文件后,使用fclose函数关闭,关闭后一定要记得将FILE*指针指向空哦。

#include<stdio.h>
int main()
{
	FILE* pf;
	pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("pf");
		return 1;
	}
	printf("创建成功\n");
	fclose(pf);
	pf = NULL;
	printf("关闭成功\n");
	return 0;
}

在这里插入图片描述

文件的顺序读写及使用

在这里插入图片描述

输入流:是用于从外部源(如键盘、文件或网络连接)读取数据的流。。
输出流:是用于将数据发送到外部目标(如屏幕、文件或网络连接)的流。
注意:输入输出是相对而言的!

fputc

在这里插入图片描述

fputc:按照顺序每次输出一个字符到流中(字符输出)

#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "w");//已只写的形式的打开指定文件,如果当前目录下没有指定文件,就在当前目录下创建一个文件
	if (pf == NULL)
	{
		perror("fopen");//如果打开失败,在屏幕上打印错误信息
		exit(-1);
	}
	fputc('s', pf);
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

fgetc

在这里插入图片描述

fgetc:按照顺序每次从流中输入一个字符(字符输入)

#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "r");//已只读的形式打开指定文件
	if (pf == NULL)
	{
		perror("fopen");//判断指定文件是否正常打开,如果打开失败在屏幕上打印错误原因
		exit(-1);
	}
	char str = fgetc(pf);
	printf("%c", str);
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

fputs

在这里插入图片描述

fputs:每次输出一行数据到流中(字符串输出)

#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		exit(-1);
	}
	fputs("asdfgjklll", pf);
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

fgets

在这里插入图片描述

fgets:每次中流中输入一行数据(字符串输入)

#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		exit(-1);
	}
	char arr[10] = { 0 };
	fputs("asdfgjklll", pf);
	fgets(arr, 10, pf); 
	printf("%s", arr);
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
注意:我们在文件中存入了一个长度为10的字符串,但是我们使用fgets取出来只有9个字符,这是因为fgets取出的字符串长度是num-1
在这里插入图片描述
那如果我们提前将文件中放入两行数据,读取两次数据会是什么结果呢?

#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		exit(-1);
	}
	char arr[10] = { 0 };
	fgets(arr, 10, pf); 
	printf("%s", arr);
	fgets(arr, 10, pf);
	printf("%s", arr);
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
哎,为什么读取两行数据打印出来却只有一行呢?因为我们第一行字符串’asdfgjklll"后是’\n’当fgets读到’\n’时停止。

#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		exit(-1);
	}
	char arr[10] = { 0 };
	fgets(arr, 10, pf); 
	printf("%s\n", arr);
	fgets(arr, 10, pf);
	printf("%s", arr);
	fgets(arr, 10, pf);
	printf("%s", arr);
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
当我们想要读取下一行的数据时,只有将这一行的数据读完才能读取下一行

一定要注意fgets停止条件有两个:

  • 当fgets读够num-1个长度的字符串时会停止,为什么读取num-1个数据是因为最后一个长度系统用来在读取到底的字符串后添加’\0’;
  • 当fgets读到’\0’时会停止。

fprintf

在这里插入图片描述

fprintf:将格式化数据输出到流中(格式化输出)

typedef struct tmp
{
	int a;
	char b;
	float c;
	double d;
	char e[10];
}tmp;
#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		exit(-1);
	}
	tmp t1 = { .a = 10,.b = 's',.c = 3.1f,.d = 3.1234567,.e = "asdfghjklx" };
	fprintf(pf, "%d %c %f %lf %s", t1.a, t1.b, t1.c, t1.d, t1.e);
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
有人会问啦,看fprintf函数类型,他里面有…,…是什么呢?…其实是可变参数。当我们使用一个形参数量为2的函数时,我们只能向这个函数传递两个实参,而可变参数这时就可以大展身手了,它可以接收你想传递的所有参数。

fscanf

在这里插入图片描述

fscanf:从流中输入格式化数据(格式化输入)

typedef struct tmp
{
	int a;
	char b;
	float c;
	double d;
	char e[10];
}tmp;
#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		exit(-1);
	}
	tmp t1 = { 0 };
	fscanf(pf,"%d %c %f %lf %s", &(t1.a), &(t1.b), &(t1.c), &(t1.d), t1.e);
	printf("%d %c %f %lf %s", t1.a, t1.b, t1.c, t1.d, t1.e);
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
到这有人可能会问了,平时使用中,printf是我们用来将数据打印到屏幕上的,那这里打印到屏幕上为什么是fscanf呢?在我们文章刚开始时就说了,输入输出是相对的。

typedef struct tmp
{
	int a;
	char b;
	float c;
	double d;
	char e[10];
}tmp;
#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		exit(-1);
	}
	tmp t1 = { 0 };
	fscanf(pf,"%d %c %f %lf %s", &(t1.a), &(t1.b), &(t1.c), &(t1.d), t1.e);
	fprintf(stdout,"%d %c %f %lf %s", t1.a, t1.b, t1.c, t1.d, t1.e);
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
我们想要使用fprintf来将数据打印到屏幕上,只需要使用stdout就可以了。

拓展

sprintf

在我们理解掌握了以上的函数,我们在拓展两个函数:
在这里插入图片描述

将格式化数据转换为字符串

  • 需要传两个参数:char* str(转换为字符串后的保存的数组)和const char* format(从const就可以知道这是一个不能修改的的数组,所以它是需要被转换为字符出串的数组)
typedef struct tmp
{
	int a;
	float c;
	char e[10];
}tmp;
#include<stdio.h>
int main()
{
	char arr[30] = { 0 };
	tmp t1 = { 10,2.3f,"asdfg" };
	sprintf(arr, "%d %f %s", t1.a, t1.c, t1.e);
	printf("%s\n", arr);
	return 0;
}

在这里插入图片描述
可以看到,我们使用打印字符串的形式就将原先结构体中的数据打印了出来。
那我们想要将转换为字符串格式的数据在转换回原来的类型可以吗?

# sscanf

在这里插入图片描述

将字符串转换为自定形式数据

  • 使用这个函数需要传两个参数:const char* s(从const就可以知道这是字符串)和const char* format(传将字符串传递到这个数组并且修改为指定的形式并且保存的数组)
typedef struct tmp
{
	int a;
	float c;
	char e[10];
}tmp;
#include<stdio.h>
int main()
{
	char arr[30] = { 0 };
	tmp t1 = { 10,2.3f,"asdfg" };
	sprintf(arr, "%d %f %s", t1.a, t1.c, t1.e);
	printf("%s\n", arr);
	tmp t2 = { 0 };
	sscanf(arr, "%d %f %s", &(t2.a), &(t2.c), t2.e);
	printf("%d %f %s", t2.a, t2.c, t2.e);
	return 0;
}

在这里插入图片描述
答案是of course!不过要注意&哦
之前的函数都是输出、输入文本信息的函数,下来就介绍输入输出二进制信息的函数

fwrite

在这里插入图片描述

fwrite:将数据块写入到流中(二进制输出 )

  • 使用fwrite需要传递四个参数:const void* ptr(被传递的地址),size_t size(这个被传递的数据的大小),size_t count(这样的数据的数量),FILE* stream(传递的流)
  • ptr处存放的count个大小为size的数据输出到stream流里面去
#include<stdio.h>
typedef struct tmp
{
	int a;
	char b;
	char arr[10];
}tmp;
int main()
{
	FILE* pf = fopen("data.txt", "wb");
	if (pf == NULL)
	{
		perror("open fail");
		exit(-1);
	}
	tmp t1 = { 1,'x',"adfsaff" };
	fwrite(&t1, sizeof(tmp), 1, pf);
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
注意:我们要对一个二进制文件进行写的操作,需要使用"wb"
在我们执行完fwrite函数后,打开data.txt文件可以看到除了adfsaff这一行字符串可以看懂,剩下的怎么是乱码啊???
这是因为fwrite函数是已二进制的形式输出到流中的。我们可以读懂的是文本文件,但是现在是二进制文件,我们写进去根本看不懂,怎么使用二进制文件中存放的数据呢?

不要着急,这就需要fread函数来帮助大家啦!

fread

在这里插入图片描述

fread:从流中读取数据块(二进制输入)

  • 从stream流里面读取count个大小为size的数据输入到ptr处的空间
#include<stdio.h>
typedef struct tmp
{
	int a;
	char b;
	char arr[10];
}tmp;
int main()
{
	FILE* pf = fopen("data.txt", "rb");
	if (pf == NULL)
	{
		perror("open fail");
		exit(-1);
	}
	tmp t1 = { 0 };
	fread(&t1, sizeof(tmp), 1, pf);
	printf("%d %c %s", t1.a, t1.b, t1.arr);
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
可以看到,我们使用了fread函数就可以轻松的将二进制文件的数据读取出来,以我们可以轻松看懂的文本形式进行打印到屏幕上。
注意:我们要对一个二进制文件进行读的操作,需要使用"rb";当二进制文件中有3个大小为size的数据,我们使用fread一次读取2个size的数据,第二次只能也只会读到1个数据。

文件的随机读写

fseek

根据文件指针的偏移量来定位文件指针
在这里插入图片描述
使用fseek函数需要传递三个参数:FILE* stream(文件信息区的文件指针),long int offset(偏移量),int origin(决定了文件指针从什么位置开始偏移)
在这里插入图片描述
SEEK_SET:从文件起始位置开始偏移
SEEK_CUR:从文件当前位置开始偏移
SEEK_END:从文件末尾位置开始偏移

#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "rb");
	if (pf == NULL)
	{
		perror("open fail");
		exit(-1);
	}
	fseek(pf, 5, SEEK_SET);//我们是从文件起始位置开始偏移的
	int ch = fgetc(pf);//fgetc将偏移得到的文件指针的数据输入到ch变量中
	printf("%c", ch);
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
我们在data.txt文本文件中存储了以下的数据,我们通过fseek函数将文件指针定位到偏移量为初始位置为5的位置,就是我们的f。
注意:这里的将文件指针进行定位不是让pf进行移动,而是让pf(文件信息区)指向的文件里面指向文件内容的指针进行移动定位
在这里插入图片描述
使用SEEK_END进行读取到离他最近的内个f呢?

#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "rb");
	if (pf == NULL)
	{
		perror("open fail");
		exit(-1);
	}
	fseek(pf, -1, SEEK_END);//我们是从文件起始位置末尾位置开始偏移的
	int ch = fgetc(pf);//fgetc将偏移得到的文件指针的数据输入到ch变量中
	printf("%c", ch);
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
使用SEEK_END就要注意我们当前文件指针是指向文件末尾的,文件从左向右进行偏移为正,从右向左进行偏移为负,偏移量为-1就指向了离当前文件指针位置最近的内个f了。

我们使用先读取几个文件中的数据,最使用SEEK_CUR进行定位,读取f呢?

#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "rb");
	if (pf == NULL)
	{
		perror("open fail");
		exit(-1);
	}
	int ch = fgetc(pf);
	printf("%c\n", ch);
	ch = fgetc(pf);
	printf("%c\n", ch);	
	ch = fgetc(pf);
	printf("%c\n", ch);
	fseek(pf, 2, SEEK_CUR);//我们是从文件当前位置开始偏移的
	ch = fgetc(pf);//fgetc将偏移得到的文件指针的数据输入到ch变量中
	printf("%c\n", ch);
	fclose(pf);
	pf = NULL;
	return 0;
}

当我们要使用SEEK_CUR时要注意:文件默认指向文件起始位置,偏移量为0(和结构体偏移量是类似的)。

ftell

返回当前文件指针相较于起始位置的偏移量
在这里插入图片描述
使用ftell函数只需要传递一个参数:文件信息区的文件指针(FILE* stream)

#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "rb");
	if (pf == NULL)
	{
		perror("open fail");
		exit(-1);
	}
	int a = fgetc(pf);
	printf("%c\n", a);
	a = fgetc(pf);
	printf("%c\n", a);
	a = fgetc(pf);
	printf("%c\n", a);
	int b = ftell(pf);//在我们进行了三次fgetc后,我们向看看文件指针的偏移量是多少,使用ftell
	printf("%d\n", b);
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
那我们在fgetc后向让文件指针指向起始位置怎么办?

rewind

让文件指针的位置回到文件起始位置
在这里插入图片描述
使用rewind函数同样只需要一个参数:文件信息区的文件指针(FILE* stream)

#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "rb");
	if (pf == NULL)
	{
		perror("open fail");
		exit(-1);
	}
	int a = fgetc(pf);
	printf("%c\n", a);
	a = fgetc(pf);
	printf("%c\n", a);
	a = fgetc(pf);
	printf("%c\n", a);
	int b = ftell(pf);
	printf("%d\n", b);
	rewind(pf);//让文件回到起始位置
	b = ftell(pf);//在看一下文件指针的偏移量是否为0
	printf("%d\n", b);
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

文本文件和二进制文件

根据数据的组织形式,数据文件被分为文本文件和二进制文件。

文本文件

数据要求在外存上加上ASCll码值的形式存储,需要在存储前进行转换。以ASCll码值的形式存储的就是文本文件。

二进制文件

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

拓展:

简单说以下内存外存的区别:

内存和外存都是存储设别;
内存是内部存储器,内存的存储速度快,存储空间小,带电存储,主要存储的是临时数据和程序;
外存是外部存储器,外存的的存储空间大,存储速度慢,不带电存储,主要存储的永久数据和程序;
如果内存中的数据没有保存到外存中就断电,保存在内存中的数据就会丢失,而外存不会出现这种情况,这就是带点存储和不带电存储的区别。

回到正题,当我们在文本文件和二进制文件中存入10000,在文件中,他们是什么结果呢?
我们首先以二进制的形式进行写入:

#include<stdio.h>
int main()
{
	int x = 10000;
	FILE* pf = fopen("data.txt", "rw");
	if (pf == NULL)
	{
		perror("open fail");
		exit(-1);
	}
	fwrite(&x,sizeof(x),1,pf);
	fclose(pf);
	pf=NULL;
	return 0;
}

在这里插入图片描述
最前面的00000000不用在意,我们是用vs编辑器打开的二进制文件,是地址。后面的10 27 00 00就是我们的10000以二进制的形式存入到内存中的结果,10000的二进制序列不是00 00 27 10吗?这是因为博主使用的vs默认是小端存储。

写入到文本文件中:

#include<stdio.h>
int main()
{
	int num = 10000;
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("open fail");
		exit(-1);
	}
	fprintf(pf,"%d",num);
	fclose(pf);
	return 0;
}

除了这种方法,我们还可以利用之前学过的sprintf将我们所需要存入到文本文件中的数据转换为字符串,然后在使用fputs存入到文本文件中去:

#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("open fail");
		exit(-1);
	}
	int a = 10000;
	char buffer[20];
	sprintf(buffer, &a);
	fputs(buffer, pf);
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "r");
	int a = fgetc(pf);
	printf("%d\n", a);
	a = fgetc(pf);
	printf("%d\n", a);
	a = fgetc(pf);
	printf("%d\n", a);
	a = fgetc(pf);
	printf("%d\n", a);
	a = fgetc(pf);
	printf("%d\n", a);
	fclose(pf);
	return 0;
}

在这里插入图片描述
为什么我们将文本文件中的1000进行取出,它取出的是49 48 48 48 48呢?
这是因为我们存入到文本文件中的数据都是以ASCll码值的形式进行存储的,1的ASCll的码值是49,而0的ASCll码值是48。从这就可以看出,我们存入到文本文件中的数据都是按照字符类型进行存储,10000按照字符是’1’,‘0’,‘0’,‘0’,‘0’,而我们是按整型的形式进行打印,所以打印出来是49,48,48,48,48。
我们如果要将10000以数字的形式进行打印,要使用%c而不是%d或者使用fscanf来进行读取:

#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "r");
	int a = fgetc(pf);
	printf("%c\n", a);
	a = fgetc(pf);
	printf("%c\n", a);
	a = fgetc(pf);
	printf("%c\n", a);
	a = fgetc(pf);
	printf("%c\n", a);
	a = fgetc(pf);
	printf("%c\n", a);
	fclose(pf);
	return 0;
}

在这里插入图片描述

#include<stdio.h>
int main()
{
	int a;
	FILE* pf = fopen("data.txt", "r");
	fscanf(pf, "%d", &a);
	printf("%d", a);
	return 0;
}

在这里插入图片描述

文件读取结束的判定

feof

feof的作用是当文件结束的时候,判断文件结束的原因是否是为遇到文件末尾结束的。
注意:feof是用来判断文件结束原因的,而不是用来判断文件是否读取结束了。
那我们怎么来判断当前位置是否时该文件的文件末尾呢?

文本文件:

  • 在我们使用fgetc的时候,当我们遇到了文件末尾,fgetc会返回EOF;
  • 在我们使用fgets的时候,当我们遇到了文件末尾,fgets会返回一个空指针。
#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("open fail");
		exit(-1);
	}
	int c;
	while ((c=fgetc(pf)) != EOF)
	{
		putchar(c);
	}
	printf("\n");
	if (ferror(pf))
	{
		puts("error");
	}
	else if (feof(pf))
		puts("end");
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
二进制文件:

  • 在我们使用fread的时候,fread会返回实际读取元素的数量,当我们遇到文件末尾的时候,fread会返回一个小于请求元素的值,通常是0。
#include<stdio.h>
#define size 5
int main()
{
	FILE* pf = fopen("data.txt", "wb");
	if (pf == NULL)
	{
		perror("open fail");
		exit(-1);
	}
	int arr[size] = { 1,2,3,4,5 };
	fwrite(arr, sizeof(*arr), size, pf);
	fclose(pf);
	pf = fopen("data.txt", "rb");
	int arr2[size] = { 0 };
	size_t ret_code = fread(arr2, sizeof(*arr2), size, pf);
	if (ret_code == size) {
		puts("Array read successfully, contents: ");
		for (int n = 0; n < size; ++n) printf("%d ", arr2[n]);
		putchar('\n');
	}
	else { // error handling
		if (feof(pf))
			printf("Error reading test.bin: unexpected end of file\n");
		else if (ferror(pf)) {
			perror("Error reading test.bin");
		}
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

文件缓冲区

ANSIC标准采用“缓冲文件系统”处理的数据文件,所谓的缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用地文件开辟一块“文件缓冲区”。从内存中向磁盘输出数据会先送到内存中地缓冲区,转满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量)中。缓冲区地大小根据c编译系统决定地。
文件缓冲区是一个内存区域,用于临时存储将要写入文件的数据。当我们使用fputs/fputc等函数来将数据存入到内存中时,难道数据是直接存入到内存中的吗?我们需要知道的是:在使用fputc、fgets等这种函数将数据存入到内存中时,不是立即将数据存入到内存中的,而是将数据存入到数据缓冲区的。而ANSIC标准中又规定了,将要存入内存中的程序数据区将数据存入缓冲区中,并且等呆缓冲区满了之后,再从缓冲区中将数据存入到内存中。从内存中读取数据时和存入的顺序相反。如果我们想要立即存入,就需要刷新文件缓存区或者关闭文件。这样做的好处是防止每次存入读取数据都会打断系统进行的进程来执行当亲存入读取的进程,大大优化了文件的读写操作,提高了效率。

害怕有人嫌字多看不下去,简单来说:系统缓冲区就是将数据存入到这个临时存储要写入文件的数据,等到缓冲区已满或者刷新缓冲区才能将缓冲区的数据写入到内存中或是读取到程序中。

在这里插入图片描述

fflush:

在这里插入图片描述

fflush函数是用来刷新缓冲区的,需要传递一个参数:FILE* stream

知道了fflush刷新缓冲区后我们就可以通过下面的代码理解上面的内容:

#include<stdio.h>
#include<windows.h>
int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("open fail");
		exit(-1);
	}
	fputs("asdfgadf", pf);//输入数据
	printf("睡眠10秒\n");
	Sleep(10000);//睡眠10秒,在这10秒内我们打开test.txt文件我们会发现文件中并没有将我们想要存入的数据存入进去
	printf("刷新缓冲区\n");
	fflush(pf);//刷新缓冲区,立即将缓冲区的内容存入到文件中
	printf("睡眠10秒\n");//这10秒内我们打开test.txt文件我们会发现我们想要存入的数据已经存入了
	Sleep(10000);
	fclose(pf);
	pf = NULL;
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
需要注意的是:我们如果不使用fflush刷新缓冲区,在我们fclose关闭文件时,也会将缓冲区的内容存入到test.txt文件中,而且我们如果不管刷不刷新缓冲区,一定要关闭文件,如果不关闭文件也没有刷新缓冲区,在程序执行结束后存到缓冲区中的数据会丢失

以上就是我们文件操作的全部内容,如果对您有所帮助,三联走一走;当然,如果文章出现错误,欢迎您在评论区或私信我指出哦~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值