C语言文件操作函数详解——将你的代码永久化 ( •̀ ω •́ )✧

  🎄博客主页:🎐大明超听话

    🎋欢迎关注:👍点赞🙌关注✍评论

    🎍系列专栏:🎑从零开始C语言

                           🎊从0开始数据结构与算法详解

                           🎆计算机考研——从零开始计算机网络

                           🎃C语言游戏制作详解

                           🎉葵花宝典之C语言冷知识

    🎪伙伴们,你们的支持对我真的很重要,欢迎👍点赞✍评论✨关注✨,欢迎各位朋友私信留言随时骚扰,私信必回。感谢你们的支持与转发!

    🎠关注我, 一同分享更优秀的内容吧!ヾ(≧▽≦*)o


🍱🍱本文重点🍱🍱:

     🎢🍛文件操作函数🍛🎢

🦺目录🦺

    🎎预备知识存储

    🎭1.为什么使用文件?🎭

    🎒2.什么是文件?🎒

    👔2.1程序文件👔

    🛵2.2数据文件🛵

    🛹2.3文件名🛹

     🚄2.4文件名表示的两种路径🚄

     🚅2.4.1.绝对路径🚅

    🚈2.4.2相对路径🚈

    🚟3.什么是文件指针🚟

    🛬文件操作函数🛬

    🛫1.文件打开和关闭函数🛫

    🚤文件打开函数:fopen

    🚢文件关闭函数:fclose

    🪂2.文件的顺序读写函数🪂

    🚥2.1 fgetc函数和 fputc函数

     💺2.2 fgets 函数和 fputs 函数💺

    🚁2.3 fscanf 函数和 fprintf 函数🚁

    ⛵3.文件的随机读写⛵

    🚏3.1 fseek函数🚏

    🏡3.2 ftell 函数🏡

    🚂3.3 rewind 函数

    🗼4.文件结束函数的使用🗼

    ⛲4.1 feof 函数的错误使用⛲

      🌌4.2正确判断文件结束🌌

    🚒文本文件和二进制文件🚒 

    🚂1.二进制输出函数fread🚂

    🗻2 二进制输出函数 fwirte🗻 

  🌏文件操作函数思维导图🌏

    🎎预备知识存储

    🎭1.为什么使用文件?🎭

    我们学习结构体时,写了通讯录的程序,当通讯录运行起来的时候,可以给通讯录中增加、删除数据,此时数据是存放在内存中,当程序退出的时候,通讯录中的数据自然就不存在了,等下次运行通讯录程序的时候,数据又得重新录入,如果使用这样的通讯录就很难受。我们在想既然是通讯录就应该把信息记录下来,只有我们自己选择删除数据的时候,数据才不复存在。这就涉及到了数据持久化的问题,我们一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据库等方式。使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化。简单的来说文件就是可以将我们的代码永久保存的东西,只要我们学会使用文件就再也不用害怕代码丢失的问题啦!

    🎒2.什么是文件?🎒

    通常情况下我们将存储在磁盘上或者硬盘上保存于同一个文档的数据就叫做文件。但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。

    👔2.1程序文件👔

    包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。简单的来说程序性文件就是我们编写代码的时候需要用到的文件,可以辅助我们代码的编写。

    🛵2.2数据文件🛵

    数据文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。简单的来说就是我们想要读取进入我们的程序的文本文件以及二进制文件都可以叫做我们的数据文件。

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

    🛹2.3文件名🛹

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

     🚄2.4文件名表示的两种路径🚄

     🚅2.4.1.绝对路径🚅

     我们的文件路径主要分为两种一种是绝对路径一种是相对路径。就比如我们从一个根目录(c:\)开始的就叫做我们的绝对路径,因为我们将我们代码的存储路径完全表示出来了。

    🚈2.4.2相对路径🚈

    我们像是只表示 test.txt 的这种形式就叫做相对路径。使用相对路径有一个条件需要我们必须在我们为表示出的路径之下:就像是假如我们已经处于c:\code的文件夹中的时候我们就可以利用相对路径进行标识我们的文件。(这涉及了一部分Linux里面的知识,我们日后会在Linux专题中详细讲述)

    🚟3.什么是文件指针🚟

    缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名FILE。所以我们通常所使用的 FILE 类型所创建的变量其实是一个结构体变量,它所指向的内容是我们创建好的文件信息的结构体。

    我们通常可以创建一个FILE*的指针变量:定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。

    🛬文件操作函数🛬

    🛫1.文件打开和关闭函数🛫

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

    🚤文件打开函数:fopen

    函数原型:

    我们可以由上图看到我们的 fopen 函数的第一个参数为我们想要打开的文件的名字,第二个参数是我们想要打开的方式。我们的第二个参数有如下几种不同的选择:

🪂文件打开方式🪂
字符                                        含                义
 “ r ”

   以只读方式,打开本地文件。

   以 “ r ”方式打开的文件,只能读出,而不能向该文件写入数据。该文件必须是已经存在的,若文件不存在,则会出错。

“ w ”

   以只写方式,创建并打开文本文件,已存在的文件将被覆盖。

   以 “ w ”方式打开文件时,无论文件是否存在,都需创建一个新的文本文件,只能写入数据。

 “  a ”    以只写方式,打开文本文件,位置指针移到文件末尾,向文件末尾添加数据,原文件数据保留。若文件不存在,则会创建一个文件。
 “  + ”   与上面的字符结合,表示以读写方式打开文本文件。即可向文件写入数据,也可从文件中读出数据。
“ b ”与上面的字符串结合,表示打开二进制文件。

        最后我们的 fopen 函数会返回一个为 FILE* 的指针(其实就是FILE结构体的指针)我们再创建一个指针变量接收即可。我们已经知道了 fopen 函数的使用细节,之后我们再来通过一组例子来感受我们 fopen 的具体使用是怎么样的吧!

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

    🚢文件关闭函数:fclose

    上面我们只是简单的打开了一个文件,但是文件在使用完毕的时候就一定得关闭,否则就会造成意想不到的错误。所以我们再来学习一下 fclose 函数。

    函数原型:

    相比于我们上面的 fopen 函数来说我们的 fclose 函数要简单的多了,只需要我们上面打开文件所接受的指针 pf 作为参数传给我们的 fclose 函数即可。我们的 fclose 函数的返回类型是 int ,这是因为我们关闭文件会有失败的情况我们可以通过 fclose 函数的返回值进而判断文件关闭是否成功。

    通过解析我们可以了解到 fclose 函数的返回值在关闭文件成功的时候会返回一个0的值,在关闭文件失败的时候会返回 EOF (也就是 -1)。同样的我们通过代码将感受 fclose 函数的使用方法。

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件

	//文件关闭
	int ret=fclose(pf);
	printf("%d", ret);
	return 0;
}

    🪂2.文件的顺序读写函数🪂

    在上面我们已经学习了文件的打开和关闭的相关的知识,所以我们接下来就来学习要怎样向文件中书写内容。在这一部分我们会学习很多新的函数,会在不同程度上帮助我们文件的书写。

    🚥2.1 fgetc函数和 fputc函数

    通常我们的文件读写函数都是成对存在的,一个函数进行文件内容的读取,一个函数进行文件的写入。我们依旧先通过函数原型学习 fgetc 函数和 fputc 函数。

    函数原型:

    我们的 fgetc 函数的参数是我们之前接收指针的指针变量,之后我们的 fgetc 函数会一个字符一个字符进行文件的读取。之后我们的函数的返回值详解如下:     我们的 fgetc 函数读取文件有如下几种情况:

                 1. 文件未到末尾,成功读取文件将我们的字符的ASCII码值作为返回值。

                 2.文件未到末尾,文件读取失败,fgetc 函数会返回 EOF 即 -1。便于我们检查。

    我们第三行和第四行的内容是我们 feof的使用方面的辨别,稍后我们会详细讲解。既然知道了我们的 fgetc 函数会读取字符串并返回 ASCII 码值,所以我们就可以利用 printf 直接进行内容的打印了。详细操作如下:

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读取文件的内容(事先我们向文件中入abcdef)
	printf("%c\n", fgetc(pf));//a
	printf("%c\n", fgetc(pf));//b
	printf("%c\n", fgetc(pf));//c
	printf("%c\n", fgetc(pf));//d
	//关闭文件
	fclose(pf);
	return 0;
}

     经过上面的验证之后我们就已经学会了 fgetc 函数的使用了,之后我们再来试着向文件中写入内容。

    函数原型:

    向文件中写入内容的操作就需要使用到我们的 fputc 函数。我们可以在函数的原型中看到,我们的 fputc 函数第一个参数为我们想要向文件中写入的内容。(我们的 char 类型数据的实质是一个ASCII 码值,也为 int 类型的)第二个参数是我们的文件指针变量。

    我们如果向文件写入成功就返回我们所写内容的 ASCII 码值。如果失败则会返回 EOF。

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//向文件写入数据
	int i = 0;
	for (i = 'A'; i < 'Z'; i++)
	{
		printf("%c ", fputc(i, pf));
	}
	
	//关闭文件
	fclose(pf);
	return 0;
}

     打开文件进行查看会发现我们的 A~Z 也确实写入了我们的文件当中。

     💺2.2 fgets 函数和 fputs 函数💺

    我们上面使用的 fgetc 函数和 fputc 函数只可以用来依次输入或者是输出一个字符,这时候就会有人会想了,这多麻烦呀。有没有函数可以直接输出一个字符串的呢?这就要说到我们的 fgets 函数和我们的 fputs 函数了。

    函数原型:

    我们这个函数的原型如上图所示,我么可以看到我们这个函数略显复杂。一共有三个参数,第一个参数为一个 char 类型的指针,是我们从文件中读取内容之后将会放到的字符数组的位置。第二个参数使我们的从文件读取的字符的个数,第三个参数是我们的文件指针变量。在这里我们有几个重要的点需要注意:

    1.当我们字符读取的个数大于我们文件中的一行字符的数量的时候,我们最大输出的字符串长度为一行的字符的个数。 

    2.当我们从文件中拿出一个字符串的时候需要将我们的 num 刻意的增大一,因为我们从文件中读取字符串的时候会自动增添一个 ‘ \0 ’ 我们的 ‘ \0 ’ 同样占据我们的 num 的大小。

    3.我们需要创建一个字符数组进行数据的使用,我们可以通过一张图片来增加我们的认识。

     最后我们函数的返回类型是用于判断我们的 fgets 函数是否从文件中成功读取了数据了的。通过资料可以知道:

    我们的 fgets 函数假如成功调用的话就会返回我们字符串的首地址,函数读取失败同样会返回NULL。我们可以将这一点作为函数是否成功调用的关键。

     示例一:

      示例二:

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//从文件中读取字符串(提前像我们的文件中写好内容)
	char ch[20] = "0";
	printf("%s", fgets(ch, 10, pf));
	
	
	//关闭文件
	fclose(pf);
	return 0;
}

     上面就是我们的 fgets 函数的使用方法,之后我们需要学习的是我们的 fputs 函数。

    函数原型:    我们的 fputs 的实际含义和我们的 fgets 函数大致相同,只不过就是方向反了一下。fputs 表示我们需要建立一个字符数组,之后我们需要将字符数组的指针交给我们的函数的第一个参数,第二个参数依旧是我们的文件指针变量。那么我们重新需要认识的只有我们的 fputs 函数的指针了。    对于我们的返回值资料上面是这么描述的:假如我们的 fputs 函数调用成功就会返回一个不是负数的值,假如我们的函数调用失败就会返回一个 EOF 即 -1 。那我们直接通过例子来学习 fputs 函数的使用。

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//向文件中写入信息
	char ch[40] = "hello world\n你好";
	if (fputs(ch, pf) != EOF)
	{
		printf("文件写入成功");
	}
	
	
	//关闭文件
	fclose(pf);
	return 0;
}

    那么我们的 fgets 函数和我们的 fputs 函数也就介绍完毕了,让我们抓紧时间进入我们下一部分知识的讲解! 

    🚁2.3 fscanf 函数和 fprintf 函数🚁

    在学习完我们的字符串与文件的转化函数之后肯定会有人产生好奇:难道只能是字符串才能写入文件当中吗?要是不是字符串的话应该怎么与文件进行转化呢?别着急,我们接下来就来学习我们的按格式输入和输出文件函数。

    函数原型:

    看过函数原型之后我们可能会对这个函数感到很迷茫,为什么参数中会省略号了?我们可以通过对比我们比较熟悉的 scanf 进行理解:     我们会发现我们 fscanf 函数和我们的 scanf 的函数原型很像,只不过是前面多了一个文件指针,这时候我们就会像会不会和我们的 scanf 函数的用法其实是一样的呢?事实也确实是这样。我们在这里再向大家介绍一个概念:

      可变参数: 可变参数指的是我们函数的参数可以通过我们的输入发生改变,大小并不确定,就像是我们的 scanf("%d %s %d %d",ret1,ch,ret2,ret3); 我们可以根据自己想要输入的需求进行参数长度的任意变化,这也就是我们函数原型中的参数带有省略号的原因。

    这么一来我们的 fscanf 函数的参数也全部介绍完毕了。那我们在来认识一下这个函数的返回类型。

    当我们按一定的格式进行读取文件中的内容的时候,假如读取成功我们的 fscanf 就会返回我们读取数据的项数,(可以忽略不计)如果读取失败就会返回一个 NULL。所以通常情况下我们可以通过判断 fscanf 函数的返回值是否为 EOF 进而判断我们的函数是否使用成功。 

    需要着重强调的是我们 fscanf 函数在使用之前需要提前创建好一个与我们想要读取的文件信息格式相同的结构体,便于我们的信息在我们的屏幕上的输出和打印。

#include<stdio.h>

struct test
{
	char name[20];
	int age;
	float num;
};
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//从文件中读取字符串(提前在我们的文件中写好内容)
	struct test s1;
	fscanf(pf, "%s %d %f", s1.name, &(s1.age), &(s1.num));
	printf("%s %d %f", s1.name, s1.age, s1.num);
	
	//关闭文件
	fclose(pf);
	return 0;
}

    和我们 fscanf 函数相对应的就是我们的 sprintf 函数了,使用 fprintf 函数可以按照指定格式向我们的文件中写入数据。

    函数原型: 

    由于我们上面我们已经认识了 fscanf 函数,已经有了可变参数的概念之后我们的 fprintf 函数的理解起来就简单多了。我们的使用的方法和我们的 printf 函数依旧相同,同样需要增加一个文件指针作为参数。那我们只剩下返回值需要重新认识了。

    通过资料我们可以了解得出:我们的 fprintf 函数在使用的时候如果向文件中写入数据就会返回写入数据的元素数据的个数,如果失败就会返回一个负数,我们同样可以抓住这一点进行判断函数是否调用成功。 

#include<stdio.h>

struct test
{
	char name[20];
	int age;
	float num;
};
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//向文件中写如一定格式的数据
	struct test s1 = { "李四",26,88.88 };
	if (fprintf(pf, "%s %d %f", s1.name, s1.age, s1.num) > 0)
	{
		printf("文件写入成功");
	}
	//关闭文件
	fclose(pf);
	return 0;
}

     虽然我们浮点数的表示并不精确(浮点数在内存中存储就是不精确的)但是我们可以发现我们的数据也按照我们的要求成功写入了我们的文件当中。

    ⛵3.文件的随机读写⛵

    到上面为止我们文件的顺序读写的相关内容就已经全部讲完了。我们会发现我们上面的函数的使用大都是在我们文件的末尾进行文件内容的添加以及删除,但是针对于我们比较长的文件这样书写就会显示出一系列的弊端,我们不可能只从末尾读取或写入文件。所以为了解决这种问题我们的文件操作函数不仅包括了文件的顺序读写函数还包括了文件的随机都写的函数。

    🚏3.1 fseek函数🚏

     我们第一个要认识的随机读写函数就是 fseek 函数,同样的道理我们通过函数的原型来初步认识我们的 fseek 函数。

    函数原型:

    通过我们的函数原型中的参数可以看到,我们的 fseek 函数第一个参数和我们的文件顺序读写所用到的函数一样也为一个文件指针, 第二个函数是一个长整型类型的数据,叫做我们文件的偏移量,表示我们所要查找的内容在文件中的位置,第三个参数 int origin 我们可以将其理解为是一个常量,因为这个参数经常取三种情况:

    就像是上图表示的那样,我们的 origin 通常向函数中传参为 SEEK_SET,(从文件的开始位置开始查找字符)SEEK_CUR,(从文件的当前位置开始查找)和我们的SEEK_END。(从文件的末尾位置开始查找)。就比如说 fseek( pf ,7,SEEK_SET); 就表示我们需要从文件的开始向后跳过七个比特位进行对文件进行更改或者读取操作。 我们这个函数的返回值如下:

    当我们函数成功指向的时候就会返回0,如果执行失败的话就会返回一个非0的值。我们同样可以从我们函数的返回值中判断函数是否成功调用。 函数的使用如下:

#include<stdio.h>

int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//更改文件的读取位置,进行文件的读取
	fseek(pf, 7, SEEK_SET);//从文件的开头进行读取数据
	char ch[20] = { 0 };
	fgets(ch, 4, pf);
	printf("%s", ch);
	//关闭文件
	fclose(pf);
	return 0;
}

    🏡3.2 ftell 函数🏡

    在了解过我们的文件偏移函数的使用之后我们肯定会想的是那我们应该怎样的到我们每一个信息的偏移的位置呢?难道要我们一个一个数吗?NONONO,接下来我们就来认识一下我们的 ftell 函数,这个函数可以告诉我们在文件中具体信息的偏移量是多少我们就可以结合我们的 fseek 函数,一起判断使用了。

    函数原型:

    通过上面的函数原型我们可以知道我们的 ftell 的函数参数仅仅只有一个文件指针,那我们究竟应该怎么使用这个函数呢?ftell 函数可以将我们当前文件的偏移作为我们的返回值返回给我们,我们可以通过变量进行接收,进而使用。 我们通过一个例子来理解一下 ftell 函数的具体的使用的方法:

#include<stdio.h>
int main()
{
	//打开一个文件
	FILE* pf1 = fopen("test.txt", "w");
	if (pf1 == NULL)
	{
		perror("fopen");
		return 1;
	}
	
	//向文件中写入一部分数据
	char ch1[20] = "abcdef";
	fputs(ch1, pf1);

	//判断当前位置的偏移量
	int ret=ftell(pf1);

	//重新输入一组数据
	char ch2[20] = "12345";
	fputs(ch2, pf1);

	//关闭文件
	fclose(pf1);
	pf1 = NULL;

	//打开文件
	FILE* pf2 = fopen("test.txt", "r");
	if (pf2 == NULL)
	{
		perror("fopen");
		return 1;
	}

	//调整偏移量
	fseek(pf2, ret, SEEK_SET);

	//读取一组数据
	char ch3[20] = { 0 };
	fgets(ch3, 6, pf2);
	printf("%s", ch3);

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

     我们先通过进行第一次写入之后利用 ftell 函数获得当前位置的偏移量,之后再进行第二次文件写入,之后关闭文件。(注意正打开文件的方式要保持一致,“ r ” 就只能读文件,“ w ”就只能写文件,否则会发生错误!)重新以只读方式打开文件即可进行文件读取。

    我们的ftell函数不仅可以用来打印固定位置的文件数据还可以用来判断一个文件所占的字节的个数,就像是我们下面例子中所展示的一样:

#include<stdio.h>

int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");

	//调整光标到文件的末尾
	fseek(pf, 0, SEEK_END);

	//计算当前文件的偏移量
	int ret=ftell(pf);
	printf("%d", ret);

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

    🚂3.3 rewind 函数

    接下来我们要介绍的就是我们的 rewind 函数,其实这个函数在单独使用的时候作用并不大,只是将我们文件读取的位置更改到从我们的文件的开头开始,其作用也可以被我们的 fseek 函数所替代。所以我们在这里只是简单的认识一下这个函数就可以了。

    函数原型: 

    我们只需要向函数的参数中传入我们之前的文件指针即可。这个函数没有返回值。使用方法和上面的 fseek 函数的 SEEK_SET 参数的效果等价。可以作为我们文件内部读取位置的调整方法之一。 

    🗼4.文件结束函数的使用🗼

    ⛲4.1 feof 函数的错误使用⛲

    其实这个函数在大多数情况都是被用来判断文件是否读取到末尾,但是实质上我们的 feof 函数的主要功能并不是这样。还记得我们上面在认识函数的时候向大家介绍的函数的返回值吗?

     我们在上面说到了有一部分的返回值细节我们并没有多说,也就是我们这一部分要向大家解释的内容。我们的 feof 其实并不是用来检测是否到达文件的末尾的函数而是用来检测文件读取是否正常结束的。假如我们读取一个文件,但是我们在文件的中读取发生了错误。所以我们的系统就会自动将检测其置为 ferror 。

     我们的 feof 函数就会不为0的数字,如果没到文件没有读取到末尾或者没有被系统置为 feof 函数项,就会使用我们的 ferror 函数进行进一步的判断。

     我们的 ferror 函数也是一样的,假如文件读取失败的话就会返回一个非零的值(真),如果为0的话就会返回 0(假)。

    总结一下:

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

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

      🌌4.2正确判断文件结束🌌

    有了上面的铺垫之后我们就可以知道我们要想判断文件是否结束,那么就需要使用文件读取函数的返回值进行判断,而不是利用函数 feof 进行判断。那么我们接下来旧居一个例子帮助理解:

#include<stdio.h>
int main()
{
	//写的形式打开文件
	FILE*pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//向文件中写入数据
	int i = 0;
	for (i = 'A'; i <= 'Z'; i++)
	{
		fputc(i, pf);
	}

	//关闭文件
	fclose(pf);
	pf = NULL;

	//以读的形式打开文件
	FILE* pa = fopen("test.txt", "r");
	if (pa == NULL)
	{
		perror("fopen");
		return 1;
	}

	//读取文件
	char ch[50] = "0";
	fgets(ch, 10, pa);

	//判断是否文件读取正常读到末尾
	if (feof(pa)) //正常结束为非0
	{
		printf("文件正常到末尾");
	}
	else if (ferror(pa)) //异常为非0
	{
		printf("文件读取异常");
	}
	else
	{
		printf("文件读取未结束");
	}

	//关闭文件
	fclose(pa);
	pa = NULL;

	return 0;
}

     我们向文件中放入 A~Z 如上图所示,之后我们读取30个字符(多出部分函数自动结束)运行结果是文件读取正常结束并到达末尾,我们读取10个字符,运行结果是文件还未读取完毕。上面就是我们 feof 函数正确的使用的方法,我们可以利用上面所示的方法判断文件是否正常结束。

    🚒文本文件和二进制文件🚒 

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

    通常情况下我们的文本文件在外部可以看看懂文件中所写的内容,但是我们的二进制文件就很难看懂文件所存入的内容了。具有一定的外部保密性。但是保密性又不是那么的强,因为对于高技术的程序员来说将我们的二进制文件转换成为文本文件简直是小菜一碟。由于我们的文本文件和二进制文件的读取和写入的函数不同。我们再来学习一下二进制文件读取写入文件时所需要的函数。

    🚂1.二进制输出函数fread🚂

     同样的道理学习一个新的函数需要从我们的函数原型开始。

     函数原型:

    通过函数原型的显示我们可以发现我们函数的参数一共有四个。第一个参数和我们的 fgets 函数的功能很相似,都是一个用于保存我们数据的指针。只不过返回类型是 void * 类型的, 也就是说我们可以像函数中传入任意的数据类型,在函数的内部我们的数据会自动转化(就像是我们的qsort函数的实现一样)第二个参数是我们想要拿出的元素的单个大小,第三个参数是我们拿出元素的个数,第四个参数是一个文件指针。最后是我们函数的返回类型

    我们的 fread 函数的返回值为我们所读取到的元素的个数,假如我们文件中的元素数量不够的话就会返回读到的元素的个数,我们也可以根据这个特点进行判断二进制文件是否读取完毕。由于我们二进制文件的读取的最好是二进制文件,所以我们再次先了解完 fwrite 函数之后再来给大家展现代码效果。

    🗻2 二进制输出函数 fwirte🗻 

    我们的二进制输出函数是一个向文件中写入二进制数据的函数。我们利用这个函数可以将我们程序中的 int 类型,char 类型等等的数据以二进制的形式写入文件当中。文件中存储二进制的时候我们是不一定能够从文件中读懂的。这是因为假如我们存储的原本就是 char 类型的数据的话,我们的 ASCII 码原本就是二进制,就不需要转换,如果是其它类型的数据的话就需要转换,就读不懂。

    函数原型:

    我们可以发现的是我们 fwrite 函数的原型的参数和我们的 fread 函数的参数一样。两者之间的含义其实也是一样的,只不过 fwrite 函数的效果是将我们的 fread 函数反过来。

    两者之间的关系 就像是我们上图中所表示的一样。那么对于 fwrite 函数的参数我们就不做过多的介绍了。直接让我们来看看 fwrite 函数的返回值。     我们可以通过资料读出,我们的函数假如成功写入的话就会返回成功写入元素的个数,也就是我们和我们的 count 参数大小相同,假如我们的函数写入失败的话就会返回一个于我们的 count 参数大小不同的值,并将我们的 ferror 的值置为1,表示我们文件写入异常。那么接下来让我们结合一下 fread 函数和我们的 fwrite 函数举一个例子:

#include<stdio.h>

struct test
{
	char ch[10];
	int age;
	float num;
};

int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "wb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//向文件中写入数据
	struct test s1 = { "张三",17,16.6 };
	fwrite(&s1, sizeof(s1), 1, pf);

	//关闭文件
	fclose(pf);
	pf = NULL;

	//打开文件
	FILE* pa = fopen("test.txt", "rb");
	if (NULL==pa)
	{
		perror("fopen");
		return 1;
	}
	//从二进制文件中读取数据
	struct test s2={0};
	//fread(&s2, sizeof(struct test), 1, pa);
	fread(&s2, sizeof(struct test), 1, pa);
	printf("%s %d %f",s2.ch,s2.age,s2.num);

	//关闭文件
	fclose(pa);
	pa = NULL;


	return 0;
}

   🎇 结尾:🎇

      到此为止我们本次博客也就正式结束了,感谢您的观看。创作不易,关注博主为你带来更加优质的作品。那么祝您天天开心,下次再见啦!( •̀ ω •́ )✧

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

阿白逆袭记

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

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

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

打赏作者

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

抵扣说明:

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

余额充值