【C进阶】文件操作

⭐博客主页:️CS semi主页
⭐欢迎关注:点赞收藏+留言
⭐系列专栏:C语言进阶
⭐代码仓库:C Advanced
家人们更新不易,你们的点赞和关注对我而言十分重要,友友们麻烦多多点赞+关注,你们的支持是我创作最大的动力,欢迎友友们私信提问,家人们不要忘记点赞收藏+关注哦!!!


前言

文件似乎不是那么很重要,但倘若大家认真学习学习文件,就会发现里面的妙处,我们能通过文件去进行读写等操作然后将我们写的代码进行读和写到我们的磁盘里面,似乎很难的样子,但经过这篇博客的讲解,相信大家会很好的理解文件的操作。
本章重点

  1. 为什么使用文件
  2. 什么是文件
  3. 文件的打开和关闭
  4. 文件的顺序读写
  5. 文件的随机读写
  6. 文本文件和二进制文件
  7. 文件读取结束的判定
  8. 文件缓冲区

这幅图,大家可能看不懂,但当大家学习完下述的知识以后就轻松掌握这张图片,并能够自行画出哦!!!
在这里插入图片描述

PS:我们此篇博客用的都是以下文件:
在这里插入图片描述

一、为什么使用文件

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

二、什么是文件

磁盘里面的东西就是文件:在这里插入图片描述

(一)程序文件

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

(二)数据文件

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

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

(三)文件名

一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含3部分:文件路径+文件名主干+文件后缀
例如:D:\code\(文件路径)test(文件主干).txt(文件后缀)
为了方便起见,文件标识常被称为文件名。


三、文件的打开和关闭

(一)文件指针

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

每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE。
每打开一个文件,就会在内存中存放这个文件的相关信息,而这个文件信息区里面的信息是被保存在一个struct FILE;结构体变量中。
在这里插入图片描述
这是FILE的定义:
在这里插入图片描述
不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,我们不必关心细节。一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。这是FILE*的指针变量:
在这里插入图片描述
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。

这个与上述的图相对应,接下来我们看一下文件的打开和关闭:

(二)文件的打开和关闭

1.简单介绍

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。
在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。
ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件
看看MSDN里面对于fopen和fclose的介绍
fopen就是打开文件:
在这里插入图片描述
在这里插入图片描述

而fclose其实相当于free的操作与做法:
在这里插入图片描述

2.打开关闭代码操作(r只读操作)

大家可能感觉还是不是很了解,那就用代码看一下:

#include<stdio.h>
//D:\\GITTE chuantimu\\test_1_17(【C进阶】文件操作)\\test.txt - 绝对路径
//如果test.txt和这个工程处在同一个文件夹里,那就是相对路径
int main() {
	FILE* pf = fopen("D:\\GITTE chuantimu\\test_1_17(【C进阶】文件操作)\\test.txt", "r");
	//FILE* pf = fopen("test.txt", "r");
	//打开失败
	if (pf == NULL) {
		perror("fopen->pf");
		return 1;
	}
	//打开成功
	else {
		printf("打开文件成功\n");
	}
	//读文件
**加粗样式**	//……
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

1.D:\GITTE chuantimu\test_1_17(【C进阶】文件操作)\test.txt - 绝对路径
2.如果test.txt和这个工程处在同一个文件夹里,那就是相对路径

如下图:

绝对路径在这里插入图片描述
在这里插入图片描述

相对路径:
打开方式:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.例子:使用w操作

这里必须先介绍一下fputs函数的概念,相信大家肯定一看就懂,无非就相当于一个scanf:
在这里插入图片描述
那我们写代码:

#include <stdio.h>
int main(){
	//打开文件
	FILE* pFile = fopen("D:\\GITTE chuantimu\\test_1_17(【C进阶】文件操作)\\test.txt", "w");
	if (pFile == NULL) {
		perror("fopen->pFile");
		return 1;
	}
	//文件操作
	else {
		fputs("fopen example", pFile);
		printf("文件写入成功\n");
	}
	//关闭文件
	fclose(pFile);
	pFile = NULL;
	return 0;
}

来看效果:
在这里插入图片描述
在这里插入图片描述
ps:这可不是我在记事本里手写的哦!!!


四、文件的顺序读写

先上张图片:在这里插入图片描述
大家可能云里雾里的,那就直接往下看,下面会有详细的介绍和代码:

(一)用fputc写文件

在这里插入图片描述

在这里插入图片描述
大家发现没有这个程序就是上面的程序,我没有在test.txt文件中把原本的内容删除,而直接替换了,这是因为’w’这个读的操作是先把原有数据清空(销毁)再进行写入的,而这个写入是如何写入的呢?是顺序写入。
代码如下:

#include <stdio.h>
int main() {
	//打开文件
	FILE* pFile = fopen("D:\\GITTE chuantimu\\test_1_17(【C进阶】文件操作)\\test.txt", "w");
	if (pFile == NULL) {
		perror("fopen->pFile");
		return 1;
	}
	char ch = 0;
	for (ch = 'a'; ch <= 'z'; ch++) {
		fputc(ch, pFile);
	}
	printf("文件写入成功\n");

	//关闭文件
	fclose(pFile);
	pFile = NULL;
	return 0;
}

(二)用fgetc读文件

在这里插入图片描述

读文件用’r’与fgetc配合,fgetc只能一个字符一个字符读取。
在这里插入图片描述
代码如下:

#include <stdio.h>
int main() {
	//打开文件
	FILE* pFile = fopen("D:\\GITTE chuantimu\\test_1_17(【C进阶】文件操作)\\test.txt", "r");
	if (pFile == NULL) {
		perror("fopen->pFile");
		return 1;
	}
	//读文件
	int i = 0;
	for (i = 'a'; i < 'z'; i++) {
		int ch = fgetc(pFile);
		printf("文件读入成功,结果为>");
		printf("%c\n", ch);
	}
	//关闭文件
	fclose(pFile);
	pFile = NULL;
	return 0;
}

(三)用fputs写文件

在这里插入图片描述
跟fputs差不多,这个是输入字符串的函数,是一整个字符串输进去。
在这里插入图片描述
代码如下:

#include<stdio.h>
int main() {
	FILE* pFile = fopen("D:\\GITTE chuantimu\\test_1_17(【C进阶】文件操作)\\test.txt", "w");
	if (pFile == NULL) {
		perror("fopen->pf");
		return 1;
	}
	//写文件
	//写一行数据
	fputs("hello world!", pFile);
	fputs("hello world", pFile);
	printf("文件写入成功\n");
	//关闭文件
	fclose(pFile);
	pFile = NULL;

	return 0;
}

(四)用fgets读文件

在这里插入图片描述
实际上就是将拿出来的字符存放在新开辟的一个数组中,其中需要注意的是在最后一个字符是\n,所以真正的字符个数为n-1个,所以最多的读出的字符数为n-1个字符。
在这里插入图片描述

#include<stdio.h>
int main() {
	FILE* pFile = fopen("D:\\GITTE chuantimu\\test_1_17(【C进阶】文件操作)\\test.txt", "r");
	if (pFile == NULL) {
		perror("fopen->pf");
		return 1;
	}
	//读文件
	//读一行数据
	char buf[20] = { 0 };
	//读5个是加上\n后5个字符,实际上真正的字符是4个
	fgets(buf, 5, pFile);
	printf("读入成功\n");
	printf("%s\n", buf);
	//关闭文件
	fclose(pFile);
	pFile = NULL;

	return 0;
}

看样子似乎这个函数解决了,可是,我们需要看到源头,当我们写入的时候写入了多行,而读的时候想要把所有的字符都读出来,那我一个字“莽”!直接最大字符数为max,拉满,这下总可以了吧,可是呢,记不记得我们说那个\n,也就是说,当读完第一行的所有字符的时候,读到\n以后就不会继续往下读了,也就是说只读到一行的字符。

(五)用fprintf写文件

看这个fprintf是不是有种printf的既视感,所以,我们与printf进行对比,发现后面都有argument……看似不理解,实际上我们知道,printf是可以有很多个参数的,所以就是可变参数列表,我们直接看图,然后直接书写:
在这里插入图片描述
在这里插入图片描述
代码如下:

#include<stdio.h>
struct S {
	char name[20];
	int age;
	float score;
};
int main() {
	struct S s = { "zhangsan",18,98.5 };
	FILE* pFile = fopen("D:\\GITTE chuantimu\\test_1_17(【C进阶】文件操作)\\test.txt", "w");
	if (pFile == NULL) {
		perror("fopen->pf");
		return 1;
	}
	//写文件
	//写带有格式化地写入文件
	fprintf(pFile, "%s %d %f\n", s.name, s.age, s.score);
	printf("写入文件成功\n");
	//关闭文件
	fclose(pFile);
	pFile = NULL;

	return 0;
}

(六)用fscanf读文件

fscanf与scanf一样,也是有&的,并且也是有可变参数的,所以我们先上张对比图:
在这里插入图片描述
发现无非就多了个pFile指针而已,那就直接上手写:
在这里插入图片描述

#include<stdio.h>
struct S {
	char name[20];
	int age;
	float score;
};
int main() {
	struct S s = { 0 };
	FILE* pFile = fopen("D:\\GITTE chuantimu\\test_1_17(【C进阶】文件操作)\\test.txt", "r");
	if (pFile == NULL) {
		perror("fopen->pf");
		return 1;
	}
	//读文件
	//读取格式化文件
	fscanf(pFile, "%s %d %f", s.name, &(s.age), &(s.score));
	printf("读出文件成功>");
	printf("%s %d %f\n", s.name, s.age, s.score);
	//关闭文件
	fclose(pFile);
	pFile = NULL;

	return 0;
}

(七)写入和读入总结

大家肯定会有个疑惑,我们的scanf是通过键盘去敲的,那个黑框框会出现闪烁的竖线让我们去敲,但是为什么在fscanf里面是"r"读操作!?这似乎有点不符合我们的常理,那我们画一张关系图直接简简单单来理解一下吧!
在这里插入图片描述
我们是程序,在程序的眼中,一个程序员敲键盘是输入到内存当中,用的是读入,所以是scanf,而这个程序员想在屏幕上看看,那就是输出,从内存中输出,那就是写出来;同理,当文件需要进入内存已经,需要输入函数进行读入,那就是fscanf这类了,而从内存输出进入文件里面就需要fprintf这类函数了。
ps:如果大家记不住这些读和写的操作,只需记得fp开头就是写,要用“w”,要是fg和fs开头的就是读,要用"r"操作,是可以打印出来的。


五、“流”的概念

(一)介绍

大家有没有发现一件事情,在第四章节刚开始的那张图片里最右边有个是所有输入流和所有输出流的概念,这个是什么!?那就需要好好介绍了:
在这里插入图片描述

我们知道,计算机内部存储了很多的数据,我们想把它们打印出来,放到文件里面,发到网络里面,放在移动硬盘里面,那我们称文件、网络这些属于输出设备,我们的数据需要放到不同的输出设备中时,放到文件,放到网络里面这些放的方法都是不一样的,那我们作为程序员,写完程序以后,需要把这些东西放到输出设备中,那么多不一样的方法我都要会的话那不是很麻烦吗,那计算机给我们提供了非常好的方法,它给了我们程序员一个很好的封装设备,称为“流”,我们只需要会一种就是把这些程序全部放到流里面去,让流自动进行分流去到不同的输出设备里面。这似乎不是很好理解,那就再举个例子吧!!!我是中国人,说中文,有一天呢,我结婚了,想要发请柬给金钱世界排名前100的富商和各国的世界首富那里去,好家伙,马云、马化腾那边我还能写写中文给他们他们能看懂,但是我给其他国家的很多富豪他们看不懂中文呀!这他们看不懂能来参加婚礼吗!?那肯定不行,此时我们怎么办,是不是需要用一个百度翻译,那我以中文输入给百度翻译,百度翻译自动的翻译成所有国家的语言然后就能发送了,知道了这个概念,大家就能够真正了解流的概念了,其实就是一个承担了翻译的任务,而这个流是FILE结构,是用FILE进行使用的。
在这里插入图片描述

我们说啊,一个C语言程序打开时候,默认是打开了三个‘“流”,分别叫做stdin(标准输入)、stdout(标准输出)、stferr(标准错误)。这三个“流”帮助我们输入输出,学习C语言初期的时候,我们学习的第一个程序就是用scanf,printf来操作的,当时候我不知道这两个玩意是干嘛的,就看,哇好神奇,我输入了3,运行编译出来的还是3,这个真的好神奇,其实,我们进行printf和scanf的时候,计算机已经给我们很有效的工作了,默认的三个“流”帮助我们了已经,这是计算机内部在操作,而我们进行写C语言代码的时候,不需要自行打开流,因为计算机已经默认打开了。
在这里插入图片描述

(二)所有输入输出流一部分类型演示

我们在开头讲啊,fputc,fgetc,fgets,fputs,fscanf,fprintf是可以适用于所有输入输出流,那我们已经在前面演示了用函数输入输出到文件的操作了,那我们接下来试一试用这些函数操作到其他输出设备吧!
(接下来的操作都是在演示我们进行“流”的操作,是看底层的逻辑)

使用stdin和stdout:
在这里插入图片描述

比较

scanf和printf只针对标准输入输出流,而fprintf和fscanf是针对所有输入输出流,也就是即支持标准输入输出流,也支持文件。

(三)总结

1.FILE*指针维护的是程序去找相对应的文件而进行的输入输出流的。也就是可以理解为我们进行读取文件以及写入文件甚至修改文件的时候,我们在这些fputc等的函数中加入此指针即可。
在这里插入图片描述

2.stdin,stdout和stderr这些是程序进行找相对应的屏幕、键盘外设来进行的输入输出流的。也就是我们可以理解为这几个关键字用在和FILE同样的位置就是能够维护的是屏幕和键盘的操作。
在这里插入图片描述


六、二进制输入输出

在这里插入图片描述
如图片所示,只针对文件哦~~

(一)用fwrite以二进制形式写文件

先打开MSDN看一下关于fwrite的介绍:
在这里插入图片描述
这个fwrite怎么参数那么多,我们也在图片里面一一罗列了,大家还记得之前我们提供的一张图片吗在这里插入图片描述
这张图片,所以打开二进制需要根据表格进行查询,那我们直接写代码吧!
在这里插入图片描述
这怎么记事本是这个样子,因为我们存的是二进制,那我们想打印出来二进制转换以后的样子那就需要用下面的fread函数了。
代码:

#include<stdio.h>
struct S {
	char name[20];
	int age;
	float score;
};
int main() {
	struct S s = { "zhangsan",18,98.5 };
	FILE* pf = fopen("D:\\GITTE chuantimu\\test_1_17(【C进阶】文件操作)\\test.txt", "wb");
	if (pf == NULL) {
		perror("fopen->pf");
		return 1;
	}
	//写文件
	fwrite(&s, sizeof(struct S), 1, pf);
	printf("写入二进制文件成功\n");
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

(二)用fread以二进制形式读文件

让我们继续打开MSDN看一看fread的介绍,这个fread与fwrite是一样的参数:
在这里插入图片描述
fread很强大,能够将二进制的形式转码换成我们能够看懂的数,我们看不懂没关系,计算机内部能看懂并能进行转换操作,如下截图:
在这里插入图片描述
那记事本是记录的那么不可思议,那我们就试一试二进制编辑器来看一下都存了些啥吧!
在这里插入图片描述
在这里插入图片描述
哈哈,到这里我们还是看不懂,但对照着表格就能一个字符一个字符分析出来了,这里不过多分析了。

代码:

#include<stdio.h>
struct S {
	char name[20];
	int age;
	float score;
};
int main() {
	struct S s = { 0 };
	FILE* pf = fopen("D:\\GITTE chuantimu\\test_1_17(【C进阶】文件操作)\\test.txt", "rb");
	if (pf == NULL) {
		perror("fopen->pf");
		return 1;
	}
	//写文件
	fread(&s, sizeof(struct S), 1, pf);
	printf("读入二进制文件成功\n");
	printf("%s %d %f", s.name, s.age, s.score);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

七、对比一组有趣的函数

对比对比一下三组函数:
在这里插入图片描述
前面两组我们都能够根据上面的内容能够很简单的理解一下:
在这里插入图片描述

那我们重点要了解的是sscanf和sprintf:
先打开MSDN一看,我们再直接书写代码:
在这里插入图片描述
在这里插入图片描述

代码如下:

#include<stdio.h>
struct S {
	char name[20];
	int age;
	float score;
};
int main() {
	struct S s = { "zhangsan",18,98.5 };
	char buf[100] = { 0 };
	sprintf(buf, "%s %d %f", s.name, s.age, s.score);//将数据放到buf字符数组里面
	printf("%s %d %f\n", s.name, s.age, s.score);//按照字符串打印
	//还原
	struct S tmp = { 0 };
	sscanf(buf, "%s %d %f", tmp.name, &(tmp.age), &(tmp.score));
	printf("%s %d %f\n", tmp.name, tmp.age, tmp.score);//按照结构体打印

	return 0;
}

所以我们可以归纳sscanf和sprintf为:
在这里插入图片描述


八、文件的随机读写

(一)fseek

**作用:**根据文件指针的位置和偏移量来定位文件指针。

我们先来认识一下fseek吧!
在这里插入图片描述
这个fseek是包含了三个参数的,第一个参数是关于与文件进行“流”的操作,第二个参数是偏移量,是关于内存中便宜的地址,第三个参数是分为三个(当前位置,文件末尾和文件起始位置),这看似很难理解,但是我们打开文件会发现文件中有个一直闪烁的光标,这个就是指针,我们可以通过操作偏移量来操作指针指向的位置,大家可能觉得很难理解,那我们直接上图片理解:在这里插入图片描述
代码如下:

#include<stdio.h>
int main() {
	FILE* pf = fopen("D:\\GITTE chuantimu\\test_1_17(【C进阶】文件操作)\\test.txt", "r");
	if (pf == NULL) {
		perror("fopen->pf");
		return 1;
	}
	else {
		int ch = fgetc(pf);//指针指向a
		printf("%c\n", ch);//a
		ch = fgetc(pf);//指针指向b
		printf("%c\n", ch);//b
		ch = fgetc(pf);//指针指向c
		printf("%c\n", ch);//c
		//如果继续往下读,那读的是d,指针指向的是d
		//但是我们调整一下,我们不读d,我们读b
		//fseek(pf, -2, SEEK_CUR);
		fseek(pf, 1, SEEK_SET);
		ch = fgetc(pf);
		printf("%c\n", ch);//b
		fclose(pf);
		pf = NULL;
	}

	return 0;
}

这串代码偏移量很好算,口算即可,但是碰到那种很复杂的怎么算偏移量呢?是个比较棘手的问题,那么计算机又又又懂我们了,给了我们ftell函数,让我们能够进行算偏移量。

(二)ftell

作用:返回文件指针相对于起始位置的偏移量。

在这里插入图片描述
这其实是最简单的了,就直接用文件指针即可:
在这里插入图片描述
这里就给截图即可,代码只需在后面加上打印这个偏移量即可。

(三)rewind

作用:让文件指针的位置回到文件的起始位置。

MSDN看介绍:
在这里插入图片描述

在这里插入图片描述
这里放总代码:

#include<stdio.h>
int main() {
	FILE* pf = fopen("D:\\GITTE chuantimu\\test_1_17(【C进阶】文件操作)\\test.txt", "r");
	if (pf == NULL) {
		perror("fopen->pf");
		return 1;
	}
	else {
		int ch = fgetc(pf);//指针指向a
		printf("%c\n", ch);//a
		ch = fgetc(pf);//指针指向b
		printf("%c\n", ch);//b
		ch = fgetc(pf);//指针指向c
		printf("%c\n", ch);//c
		//如果继续往下读,那读的是d,指针指向的是d
		//但是我们调整一下,我们不读d,我们读b
		fseek(pf, -2, SEEK_CUR);
		//fseek(pf, 1, SEEK_SET);
		ch = fgetc(pf);
		printf("%c\n", ch);//b
		printf("%d\n", ftell(pf));
		rewind(pf);
		ch = fgetc(pf);
		printf("%c\n", ch);
		fclose(pf);
		pf = NULL;
	}

	return 0;
}

九、文本文件和二进制文件

根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件
一个数据在内存中是怎么存储的呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节

我们假如想存个10000这个数,有两种不同的存储方式,一种是ASCII码值存储,另一种是二进制形式存储,ASCII码值存储是先把10000分隔开5个字符,根据字符在ASCII码值内的转化为10进制数再转化成二进制存储;二进制存储是直接将10000转化成二进制存储起来,似乎很难理解,那就来张图片理解理解:

在这里插入图片描述
字符’1’对应的ASCII码值为49,49的二进制为00110001;字符‘0’也同样。
我们通常看到的内存是十六进制,我们看一下10000在内存中的存储:
在这里插入图片描述
VS是小端存储的,所以拿出来就是0x00 00 27 10 转换成二进制为00000000 00000000 0010011 00010000所以是二进制存储的。
那一个代码测试不过瘾,再来一个:
在这里插入图片描述
在这里插入图片描述


十、文件读取结束的判定

(一)被错误使用的feof

在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。

  1. 文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
    例如:
    fgetc 判断是否为 EOF .
    fgets 判断返回值是否为 NULL .
  2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数
    例如:
    fread判断返回值是否小于实际要读的个数。

(二)feof与ferror区别

首先是文件读取结束了
结束后我想知道读取结束的原因
feof - 返回真,就说明是文件正常读取遇到了结束标志而结束的。
ferror - 返回真,这说明是文件在读取过程中出错了而结束。

//测试代码1
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
	int c; // 注意:int,非char,要求处理EOF
	FILE* fp = fopen("D:\\GITTE chuantimu\\test_1_17(【C进阶】文件操作)\\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");
	else if (feof(fp))
		puts("::End of file reached successfully");
	fclose(fp);
}

在这里插入图片描述

//测试代码2:
#include <stdio.h>
enum { SIZE = 5 };
int main(void)
{
	double a[SIZE] = { 1.,2.,3.,4.,5. };
	FILE* fp = fopen("D:\\GITTE chuantimu\\test_1_17(【C进阶】文件操作)\\test.txt", "wb"); // 必须用二进制模式
	fwrite(a, sizeof * (a), SIZE, fp); // 写 double 的数组
	fclose(fp);
	double b[SIZE];
	fp = fopen("test.bin", "rb");
	size_t ret_code = fread(b, sizeof * b, SIZE, fp); // 读 double 的数组
	if (ret_code == SIZE) {
		puts("Array read successfully, contents: ");
		for (int n = 0; n < SIZE; ++n) printf("%f ", b[n]);
		putchar('\n');
	}
	else { // error handling
		if (feof(fp))
			printf("Error reading test.bin: unexpected end of file\n");
		else if (ferror(fp)) {
			perror("Error reading test.bin");
		}
	}
	fclose(fp);
}

十一、文件缓冲区

ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
如下图演示:
在这里插入图片描述
我们来一个代码体会体会吧!

#include <stdio.h>
#include <windows.h>
//VS2022 WIN11环境测试
int main()
{
	FILE* pf = fopen("D:\\GITTE chuantimu\\test_1_17(【C进阶】文件操作)\\test.txt", "w");
	fputs("abcdef", pf);//先将代码放在输出缓冲区
	printf("睡眠10秒-已经写数据了,请打开test.txt文件,看文件有没有内容\n");//没有内容
	Sleep(10000);
	printf("刷新缓冲区\n");
	fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
	printf("再睡眠10秒-此时,再次打开test.txt文件,看文件有没有内容\n");//有内容了
	Sleep(10000);
	fclose(pf);//fclose在关闭文件的时候,也会刷新缓冲区
	pf = NULL;
	return 0;
}

在这里插入图片描述
所以,因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。


总结

文件操作是十分的有趣的,能够从原本只是闭塞的C语言调试台能够在磁盘中显示出来,是很好用的一种方法,以后就不会担心自己的代码太长无法复制到微信传给朋友了,而现在可以送到文件txt中将这些代码全部C过去,是很好用的!!!


客官,阅读到这儿了,来个三连支持一下吧!!!

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

2022horse

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

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

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

打赏作者

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

抵扣说明:

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

余额充值