C语言进阶——文件操作

🌳🌲🌱本文已收录至:C语言——梦想系列

更多知识尽在此专栏中!

🎉🎉🎉欢迎点赞收藏关注 🎉🎉🎉


目录

🌳前言

🌳正文

🌲关于文件

🌱什么是文件?

🌱文件有什么用?

🌱文件的格式是什么?

🌲使用文件

🌱文件指针

🌱文件的打开和关闭

🌲顺序读写

🌱输入、输出流

🌱fputc 与 fgetc

🌱fputs 与 fgets

🌱fprintf 与 fscanf

🌱fwrite 与 fread

🌲随机读写

🌱fseek

🌱ftell

🌱rewind 

🌱fseek、ftell、rewind 三合一

🌲文本文件与二进制文件

🌱文本文件

🌱二进制文件

🌱注意 

🌲文件使用注意事项

🌱被错误使用的feof

🌱文件读取结束原因判断

🌱文件缓冲区

🌳总结


🌳前言

  文件——是我们生活中必不可缺的一部分,优秀的文件管理能使我们工作效率更高,比如上学时的点名册、平时记账的手账本、电脑中存储数据的各种文件夹等。数据构成文件,文件成就数据,因此我们需要学习C语言中的各种文件操作,使数据能够做到持久化存储。

图片来源:百度图片

🌳正文

  文件操作涉及到的内容还是比较多的,大致可分为三个问题:是什么? 怎么用? 要注意什么? 从这三个问题可以衍生出很多问题,其中怎么用是内容最丰富的版块,让我们直接进入正题吧。

🌲关于文件

🌱什么是文件?

  如上图所示,这就是文件,不过这是传统的纸质文件。在我们电脑中的都是电子文件夹,存储的都是电子文件,比如数字、图片、文档等,这些数据都是存储在我们电脑的硬盘上的,只要硬盘没坏,那么这些数据随时随地都能找到,而且方便检索。在程序设计中,我们一般将文件分为两种:程序文件数据文件(从文件功能的角度分类),本文主要介绍的是数据文件。

程序文件

包括源程序文件,比如我们的 .c 文件;目标文件,经过预编译、编译、汇编后生成的目标文件,后缀为 .o ,对其进行链接后,就能生成可执行程序;当然最后一种就是可执行程序文件,后缀为.exe

数据文件

就像上图一样,主要存储的是各种数据信息,数据文件的职能是能让程序读取到数据,以及能够对其写入数据,这些数据是能够持久化存储的。

🌱文件有什么用?

电脑C盘中存储的各种信息

  文件可以保存数据,使数据能做到持久化存储。文件可以使我们的操作更为合理,比如现在写的这篇博客,本质就是一个文件,不过是存储在服务器上的文件(数据)。电子文件的最大特点就是易于检索了,这也正是电脑的优点之一。至于C语言中的文件可以用于保存程序运行所产生的数据,比如通讯录系统,可以将联系人信息保存到文件中,现在的程序设计数据一般都是存储在数据库中,毕竟本地文件夹安全性还是比较低。

🌱文件的格式是什么?

  所有文件都有唯一的标识符,标识符可以分为三部分:文件路径+文件名主干+文件后缀,比如存储在我电脑中的VS文件标识符为:

C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE

  为了方便称呼,我们一般将其称为文件名,比如 devenv.exe 就是一个典型的程序文件

🌲使用文件

🌱文件指针

  文件是一个庞大的集合体,类似于结构体,不过更为复杂,因此在C语言中有一个专门的指针 文件类型指针,简称为 文件指针 用来指向文件首地址。系统会将文件规范化,当使用文件时,系统会在内存中开辟一个对应的文件信息区,这个信息区中包括了文件的各种信息(文件名、文件状态、文件位置等),如果对应信息缺失,系统会自动补齐。前面说过,文件类似于结构体,因此整个文件信息是保存在一个庞大的结构体中的,为了与传统结构体区分开,专门创建了 FILE* 这种特殊的指针,即文件指针。

  因为VS2019将其分的太细了,这里不好演示,但知道 FILE 这个东西本质是个结构体就行了 

🌱文件的打开和关闭

  文件得先打开,才能关闭,最好跟动态内存管理一样,有申请就要有释放,成对出现更为安全。先来说说打开吧!

🪴文件打开

  文件打开用的是 fopen 这个函数,fopen 的作用是从一个文件中以某种方式打开文件,返回类型是 FILE* 即打开文件的起始地址,因此我们需要用一个 FILE* 类型的指针来接收。

注意:文件打开后,要对文件指针进行判断,如果指针为空,说明文件打开失败,此时要报错,并终止后续操作 

if (NULL == fp)
{
	//报错函数,说明此文件打开失败
	perror("fopen::test.txt!");
	return 1;//错误结束
}

🪴目标文件

  有两种形式,一种是绝对地址,另一种是相对地址

绝对地址

即唯一路径,使用绝对地址访问文件时,文件可以在电脑中的任意位置,前提是地址要合法。绝对位置的文件标识符必须全,即文件路径+文件名主干+文件名后缀

比如 C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE

注意:

使用绝对路径时,需要在每个 \ 前额外加一个 \  原因很简单,单个 \ 与其他字符结合,可能会变成一个转义字符,因此需要两个 \\ 来表示一个 \  

如果是 Mac 就不需要担心这个问题,因为它用的是 / 

//绝对,指此地址是唯一的,能通过这个地址找到唯一的文件
FILE* fp = fopen(" C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\Common7\\IDE", "w");

相对位置

此时的路径是固定的,一般和当前源文件处于同一位置,相对嘛,就是相对于当前程序文件。相对位置只需要文件名主干+文件后缀就行了 。

比如 devenv.exe ,此时存储位置相对于上面的绝对地址,位于同一目录下

//相对,指在当前工程文件内的文件
FILE* fp = fopen("test.txt", "w");

🪴打开方式 

  文件打开方式有很多种,比如只读、只写、读+、二进制写等……

  值得注意的是当我们通过的方式打开文件时,如果目标文件不存在,那么打开就会失败;但如果是通过的方式打开文件时,如果文件不存在,会自动创建一个目标文件

  下面来演示下用写的方式打开文件,然后文件不存在,自动创建文件的情况:

注意:这种是文件的标准使用方式,即先打开,然后判断是否打开失败,如果失败就报错,否则就可以使用文件,最后再关闭文件

//文件创建,通过程序创建
int main()
{
	//可以利用只读的特性:没有就会自己创建
	//这里是相对路径
	FILE* fp = fopen("test.txt", "w");//打开
	if (NULL == fp)
	{
		//报错函数,说明此文件打开失败
		perror("fopen::test.txt!");
		return 1;//错误结束
	}

	//进行文件操作……//

	fclose(fp);//关闭
	fp = NULL;//置空
	return 0;
}

注意:

  • 这个特点很好用,但也很致命,因为每次写文件,都相当于在覆盖文件,假如我们想对原文进行追加,就需要创建原来的数据,再创建新数据,然后一起写入文件中。其实面对这种场景,C语言还提供了另一种文件打开方式 追加 "a" ,下面就是各种打开指令的集合表。
文件打开指令含义如果目标文件不存在
"r"        只读打开一个文件,只能对这个文件进行读取操作打开失败,报错
"w"        只写打开一个文件,只能对这个文件进行写入操作建立目标文件
"a"        追加向文件末尾处添加数据建立目标文件
"rb"        只读打开二进制文件,只能对其进行二进制读取打开失败,报错
"wb"        只写打开二进制文件,只能对其进行二进制写入建立目标文件
"ab"        追加向二进制文件末尾处添加数据追加失败,报错
"r+"        读写打开一个文件,可以进行读取和写入操作打开失败,报错
"w+"        读写打开一个文件,可以进行读取和写入操作建立目标文件
"a+"        读写对文件末尾处进行读取和写入操作建立目标文件
"rb+"        读写打开二进制文件,可以进行二进制读取和写入打开失败,报错
"wb+"        读写打开二进制文件,可以进行二进制读取和写入建立目标文件
"ab+"        读写对二进制文件末尾处进行读取和写入操作建立目标文件

🪴文件关闭

  文件关闭用到的是 fclose 这个函数,fclose 就比较简单了,只需要一个参数—文件指针(FILE*),然后就能关闭这个文件指针所指向的文件,感觉有点像 free,功能强大,使用方便。同 free 一样,fclose 关闭文件后,也需要将指针(文件指针)置空,避免出现野指针

fclose(fp);//关闭
fp = NULL;//置空

🌲顺序读写

🌱输入、输出流

  在介绍文件读写操作前,需要先说明一下C语言中“流”(format)的概念。假设将数据看作水流,那么它就有两个关键部分:从哪里流出(源头)、流入哪里(终点),其中流出可以看作输出流入可以看作输入。C语言中有三种流:标准输入输出流、文件输入输出流、二进制输入输出流(实际使用时用前两种流,第三种的目标流一般为文件)。

🪴标准输入输出流

  标准输入输出流(I/O)包括标准输入流(stdin)—从键盘输入标准输出流(stdout)—从屏幕输出标准错误流(strerr)—从屏幕输出,任何一个C程序,只要运行起来都会默认打开以上三个流,比如我们常用的 scanf、printf就是基于标准输入输出流而运行的,这也正是二者需要键盘、屏幕的原因。

🪴文件输入输出流

  顾名思义,文件输入输出流所依赖的载体为文件,无论是输入还是输出数据,都是在文件上进行的,因此它的对象类型为 FILE* ,文件输入输出流可以使用所有输入输出流函数,比如fputc、fprintf、fscanf等,使用时只需要加上目标流类型就行了。

🪴二进制输入输出流

  二进制输入输出流主要适用于文件操作,对文件进行二进制数据的读取和写入,所以二进制输入输出一般用在文件操作中。二进制只有0、1这两个数,因此如果我们使用二进制输出流对某个文件进行写入,文件中存储的信息就变成了一串二进制数(可以使用二进制文件查看器观察),如果用普通文本的形式查看此文件,会得到一串乱码。二进制输入输出流有fwrite、fread这两个函数。

注意:

  • printf、scanf、gets等这种不需要指定目标流的函数,设计时就已经规定好了,它们是标准输入输出流函数。而fprintf、fscanf、fgets等这些面向所有输入输出流的函数更为原始,需要用户使用时根据具体情况选择目标流,所以这些函数也能实现标准输入输出流函数的功能,只需要把目标流写成 stdin(输入)、stdout(输出)就行了。下面是各种输入输出函数的集合表:
功能函数名适用于(目标流)
进行单字符的输入fgetc                 所有输入流                
进行单字符的输出fputc所有输出流
文本行输入函数(读取一行数据)fgets所有输入流
文本行输出函数(写入一行数据)fputs所有输出流
格式化输入函数fscanf所有输入流
格式化输出函数fprintf所有输出流
二进制输入函数fread文件输入流
二进制输出函数fwrite文件输出流

注意:为了方便函数的介绍,接下来会先介绍写入(输出),再介绍读取(输入)函数

🌱fputc 与 fgetc

  fputc 对文件进行单字符的写入fgetc 读取文件中的单字符

🪴fputc

//文件读写之逐字符写
//因为没有数据,所以我们先写再读
int main()
{
	//打开
	FILE* fp = fopen("test.txt", "w");
	if (NULL == fp)
	{
		perror("fopen::test.txt!");
		return 1;
	}

	//进行操作
	char* pc = "abcdef123";
	//逐字符写入
	while (*pc)
	{
		fputc(*pc, fp);//逐字符放
		pc++;
	}

	//关闭
	fclose(fp);
	fp = NULL;
	return 0;
}

🪴fgetc 

//文件读写之逐字符读
int main()
{
	FILE* fp = fopen("test.txt", "r");
	if (NULL == fp)
	{
		perror("fopen::test.txt!");
		return 1;
	}
	//逐字符读取
	int ch = 0;//需要用整型,因为EOF是-1
	while ((ch = fgetc(fp)) != EOF)
	{
		//逐字符读取后,赋给字符变量ch,然后打印
		printf("%c", ch);
	}

	//关闭
	fclose(fp);
	fp = NULL;
	return 0;
}

注意:

  • 在读取或写入字符串时,可以通过特定的条件结束读写。比如写入:可以通过字符串自带的结束标志 \0 结束写入读取:可以通过fgetc的返回值进行判读,如果返回 -1(EOF) 就说明数据已经读取完了
  • 单纯写文本数据时,要使用指令 "w" 单纯读数据时,要使用指令 "r" 指令与操作一定要匹配上,不然就会发生意想不到的错误

🌱fputs 与 fgets

  fputs 对文件进行一行数据的写入fgets读取文件中的一行数据

🪴fputs

//文件读写之行写
int main()
{
	FILE* fp = fopen("test.txt", "w");
	if (NULL == fp)
	{
		perror("fopen::test.txt");
		return 1;
	}

	char* pc = "这是由标准输入输出流写入的数据";
    fputs(pc, fp);//行写入
	fclose(fp);
	fp = NULL;
	return 0;
}

🪴fgets 

//文件读写之行读
int main()
{
	FILE* fp = fopen("test.txt", "r");
	if (NULL == fp)
	{
		perror("fopen::test.txt");
		return 1;
	}

	char tmp[30] = "0";
	fgets(tmp, sizeof(tmp), fp);//行读取
	printf("%s\n", tmp);
	fclose(fp);
	fp = NULL;
	return 0;
}

注意:

  • 行写入时,要确保写入的是字符串数据,传参数时要传地址;行读取时,需要设定待读取数据的数量,一般是跟待存储空间大小相匹配。如果行读取结束,有两种情况:1、因无法读取数据而结束   2、因读取到文件末尾而结束 
  • 单纯文本数据时,要使用指令 "w" ;单纯数据时,要使用指令 "r" 

🌱fprintf 与 fscanf

  fprintf 是对文本进行格式化数据的写入fscanf 是将文本中的数据进行格式化读取

🪴fprintf

//按照文件流格式化写入
struct S
{
	char name[20];
	int age;
	float score;
}a = { "张三",20,88.8f };
int main()
{
	FILE* fp = fopen("test.txt", "w");
	if (NULL == fp)
	{
		perror("fopen::test.txt");
		return 1;
	}
	fprintf(fp, "%s %d %.2f", a.name, a.age, a.score);//适用于所有流的格式化输入输出函数

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

🪴fscanf

//按照文件流格式化读取
struct S
{
	char name[20];
	int age;
	float score;
}tmp;
int main()
{
	FILE* fp = fopen("test.txt", "r");
	if (NULL == fp)
	{
		perror("fopen::test.txt");
		return 1;
	}
    fscanf(fp, "%s %d %f", tmp.name, &(tmp.age), &(tmp.score));//适用于所有流的格式化输入输出函数
	printf("%s %d %.2f\n", tmp.name, tmp.age, tmp.score);
	fclose(fp);
	fp = NULL;
	return 0;
}

🪴延申:sprintf 和 scanf

  除了 fprintf / fscanf printf / scanf 这两组格式化输入输出外,还存在另一组格式化输入输出函数:sprintf / sscanf

简单介绍一下,sprintf 是把格式化的数据按照一定的格式转换为字符串,相反的,sscanf 就是从字符串中按照一定格式读取出格式化的数据

sprintf sscanf 可以把结构体中的数据打包成一个字符串,也可以对某个字符串进行拆分。这个东西在我们生活中有应用,比如当我们登录账号时,会把账号、密码这个结构体打包成一串字符串,交给后端处理,当然有个更高级的名词:序列化与反序列化

注意:

  • printf 输出家族返回的是实际写入(输出)的字符总数(包括转义字符),而 scanf 输入家族返回的是实际读取(输入)的元素个数。举个栗子,字符串 abc ,输出返回 3,输入返回 1,因为此时的字符串视为一个元素。
  • 单纯文本数据时,要使用指令 "w" ;单纯数据时,要使用指令 "r" 

🌱fwrite 与 fread

  fwrite 是对文件进行二进制数据的写入fread 是以二进制的形式读取文件中的数据

🪴fwrite

//文件读写之二进制写入
struct S
{
	char name[20];
	int age;
	float score;
}a = { "张三",20,88.8f };

int main()
{
	//把a中的数据写到文件中
	FILE* fp = fopen("test.txt", "wb");
	if (NULL == fp)
	{
		perror("fopen::test.txt");
		return 1;
	}
	//二进制的写文件
	fwrite(&a, sizeof(a), 1, fp);

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

🪴fread 

//文件读写之二进制读取
struct S
{
	char name[20];
	int age;
	float score;
}tmp;

int main()
{
	//把文件中的数据读取到tmp中
	FILE* fp = fopen("test.txt", "rb");
	if (NULL == fp)
	{
		perror("fopen::test.txt");
		return 1;
	}
	fread(&tmp, sizeof(tmp), 1, fp);
	printf("%s %d %.2f\n", tmp.name, tmp.age, tmp.score);

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

注意:

  • 当我们使用二进制写入数据到文件时,如果是以文本的方式打开,只能看懂字符串部分,数字部分是看不懂的,我们可以通过VS中的二进制编辑器,来观察其中的数据。
  • 单纯二进制数据时,要使用指令 "wb" ;单纯二进制数据时,要使用指令 "rb" 

🌲随机读写

  随机读写函数,需要配合上面的输入输出函数使用,所谓的随机读写,是指通过改变文件指针的偏移量,来写入或读取数据。介绍三个和随机读取有关的函数:fseek 改变文件指针偏移量ftell 查看当前文件指针的偏移量rewind 使文件指针复原至起始位置

🌱fseek

//fseek,文件指针偏移量
int main()
{
	FILE* fp = fopen("test.txt", "w");
	if (NULL == fp)
	{
		perror("fopen::test.txt");
		return 1;
	}
	char* pc = "abc";
	fseek(fp, 20, SEEK_SET);//从起点往后偏移
	fputs(pc, fp);

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

🌱ftell

//ftell,返回当前文件指针偏移信息
int main()
{
	FILE* fp = fopen("test.txt", "r");
	if (NULL == fp)
	{
		perror(fp);
		return 1;
	}
	printf("当前文件指针偏移量为:%d\n", ftell(fp));
	fseek(fp, 20, SEEK_SET);//向后偏移20
	printf("经过fseek设置后的文件指针偏移量为:%d\n", ftell(fp));
	fclose(fp);
	fp = NULL;
	return 0;
}

🌱rewind 

//rewind,使文件指针恢复至原位置
int main()
{
	FILE* fp = fopen("test.txt", "r");
	if (NULL == fp)
	{
		perror("fopen::test.txt");
		return 1;
	}
	fseek(fp, 20, SEEK_SET);//先让文件指针向后偏移20
	printf("当前文件指针偏移量为:%d\n", ftell(fp));
	rewind(fp);//使文件指针恢复至起始位置
	printf("经过恢复后的文件指针偏移量为:%d\n", ftell(fp));
	return 0;
}

🌱fseek、ftell、rewind 三合一

//fseek、ftell、rewind三合一
//假设文件中存储数据为abcdef
int main()
{
	FILE* fp = fopen("test.txt", "r");
	if (NULL == fp)
	{
		perror("fopen::test.txt");
		return 1;
	}
	printf("现在文件中内容为abcdef,我们要依次取出e、b、d\n");
	fseek(fp, -2, SEEK_END);//从后往前偏移
	printf("先取出字符%c\n", fgetc(fp));
	rewind(fp);//还原至起始位置
	fseek(fp, 1, SEEK_SET);//从前往后偏移
	printf("再取出字符%c\n", fgetc(fp));
	fseek(fp, 1, SEEK_CUR);//从当前位置向后偏移
	printf("最后再取出字符%c\n", fgetc(fp));

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

注意:

  • 每进行一次文件输入输出操作,文件指针都会向后移动一位。比如上面的三合一, 当我们读取到字符 'b' 后,文件指针向后移动一位,指向字符 'c' ,此时只需要把文件指针向后偏移一位,就能愉快的读取到字符 'd' 了。

🌲文本文件与二进制文件

🌱文本文件

  文本文件指以ASCII码(文本方式)存储的数据,原始数据机器能直接看懂,将内存中的数据对应ASCII码解码存储后,我们人类也能看懂,举个栗子,在记事本中写的文本,就是文本文件

🌱二进制文件

  二进制文件是将数据编译后转成二进制形式,然后直接存储的文件,这种文件机器能秒懂,读取效率很高(因为不需要转译),但二进制一般人是看不懂的,部分二进制数据也无法通过ASCII码解码为正确的数据,因此强行输出二进制文件,极有可能会得到乱码。比如将上面的那段话通过二进制形式写入文件中,可以看到除字符类型数外,其他类型的数据变成了乱码。

  下图为上面的二进制文件在内存中以二进制形式存储的样子,显示为十六进制(节省空间),实际为二进制

🌱注意 

  • 如果待读取的文件中存储的是二进制数据,就需要使用 二进制读取 "rb" 的形式读取数据;反之如果想写入二进制数据,就需要用 二进制写入 "wb" ,无论是二进制还是普通文本,计算机都能读懂,只是我们看不得罢了。小技巧:可以使用二进制存储重要数据,这样外行人一时半会也理解不了。

🌲文件使用注意事项

🌱被错误使用的feof

  很多人在写C语言课设的时候(学生信息管理系统、通讯录系统等),会通过 feof 来判断文件是否读取结束,这是一种错误的用法,因为 feof 的作用是判断当前文件读取结束原因的,如果是因为读取到了末尾而结束,feof(fp) 就为真;除了这个以外,还有另一个文件读取结算原因判断函数,ferror ferror(fp) 为真时,说明此时发生了读取异常,并非正常结束,我们可以通过这两个报错函数来判断文件读取结束的真正原因。

	char arr[100] = "0";
	fgets(arr, sizeof(arr), fp);
	printf("%s\n", arr);
	int n = 0;
	if ((n = feof(fp)))
		printf("End by EOF\n");
	if (ferror(fp))
		printf("End by IO Error\n");

🌱文件读取结束原因判断

  既然 feof 不是用来判断读取是否结束的,那说明存在其他判断方法,其实答案就是函数设计中,前辈在设计函数时已经考虑好了,比如 fgetc 没有读取到数据会返回EOF,fgets 没有读取到数据会返回NULL,fscanf 可以通过其返回的实际读取元素个数进行判断,fread 可以通过返回值与指定读取的元素数比较。每种读取函数都有属于的自己的判断方法,比如下面这两个例子:

🪴对文本数据进行读取

//读取错误信息判断
//1.文本文件版,假设文件内已有信息,为abcdef
int main()
{
	FILE* fp = fopen("test.txt", "r");
	if (NULL == fp)
	{
		perror("fopen::test.txt");
		return 1;
	}

	int ch = 0;//接收读取的数据,要用整型,因为EOF为-1
	while ((ch = fgetc(fp)) != EOF)
	{
		printf("%c", ch);
	}
	//判断是为何结束
	if (feof(fp))
		printf("\nEnd by EOF(因读到文件末尾而结束)\n");
	else if (ferror(fp))
		printf("\nEnd by IO(因中途读取失败而结束)\n");

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

🪴对二进制文件进行读取

//2.二进制文件版
enum { SIZE = 5 };//相当于宏定义
int main()
{
	double a[SIZE] = { 1.1,2.2,3.3,4.4,5.5 };
	//首先把五个浮点数以二进制的形式,写入文件中
	FILE* fp = fopen("testFoBin.txt", "wb");
	if (NULL == fp)
	{
		perror(fp);
		return 1;
	}
	fwrite(&a, sizeof(*a), SIZE, fp);
	fclose(fp);//写入完成,关闭文件

	double b[SIZE] = { 0.0 };
	//现在以二进制的形式读取数据
	fp = fopen("testFoBin.txt", "rb");
	int size = fread(&b, sizeof(*b), SIZE, fp);
	if (size == SIZE)
	{
		printf("No Error!\n");
		int i = 0;
		while (i < size)
			printf("%.2lf ", b[i++]);
	}
	else
	{
		if (feof(fp))
			printf("\nError by EOF\n");
		else if(ferror(fp))
			printf("\nError by IO\n");
	}
	fclose(fp);
	fp = NULL;
	return 0;
}

🌱文件缓冲区

  ANSIC 标准定义了“缓冲文件系统”这个概念,所谓缓冲文件系统是指系统自动地在内存中为程序
中每一个正在使用的文件开辟一块文件缓冲区。无论是读取还是写入数据时,都会先将数据送入文件缓冲区,等文件缓冲区装满遇到刷新指令后,数据才会被读取(写入)到目标空间中。文件缓冲区的大小是由编译器决定的。

🪴验证文件缓冲区是否存在

我们可以利用睡眠函数 Sleep 来使程序暂停,此时数据还没有被写入文件中,仍然位于缓冲区;之后再手动刷新缓冲区,数据此时会被推送至文件中

//文件缓冲区
#include<windows.h>
int main()
{
	//打开文件
	FILE* fp = fopen("test.txt", "w");
	if (NULL == fp)
	{
		perror(fp);
		return 1;
	}
	char* ps = "测试文件缓冲区";
	fputs(ps, fp);//先将数据写到缓冲区中
	printf("数据现在已经在缓冲区里面了,但还没有推送到文件中\n");
	printf("程序睡眠10秒,10秒后刷新缓冲区\n");
	Sleep(10000);//睡眠函数,单位是毫秒
	fflush(fp);
	printf("现在缓冲区已经刷新,数据已经写入文件中了\n");
	Sleep(10000);

	//关闭文件,当文件关闭时,缓冲区也会被刷新
	fclose(fp);
	fp = NULL;
	return 0;
}

  可以看到文件缓冲区是真实存在的。

注意:

  • fclose 关闭文件后,会自动刷新缓冲区,数据能够推送至文件
  • 当程序运行结束后,缓冲区也会被自动刷新
  • scanf 遇到 \n 也会触发缓冲区刷新,另外如果其在读取字符型数据时,遇到空白字符(空格TAB键)也会触发缓冲区的刷新

🌳总结

  以上就是C语言文件操作的所有内容了,从文件的打开到文件的关闭,中间可以进行多种操作,构造出巧妙的数据。当然前提是我们得学会文件的相关操作,可以巧记为单字符读写、行读写、格式化读写和二进制读写,无论是那种操作,都需要和对应的文件操作指令匹配上;关于随机读写,记住那三个偏移量函数就行了;最后需要对文件缓冲区有一定的理解,确保数据能成功推送至文件内。总之,文件操作的学习可以宣布毕业了。

  如果你觉得本文写的还不错的话,期待留下一个小小的赞👍,你的支持是我分享的最大动力!

  如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正

相关文章推荐

C语言进阶——动态内存管理

C语言进阶——自定义类型

C语言进阶——指针进阶试题讲解(万字长文详解)

  • 63
    点赞
  • 94
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 68
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 68
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北 海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值