为什么使用文件,我们每当运行C语言程序运行时,我们可以在程序中进行一系列操作,但是每当我们退出程序时,保存在内存中的数据就自动销毁了,这就会导致下次程序运行我们又要重新录入。这时我们可以使用文件操作将数据存放在硬盘上,在我们下次运行程序时,将硬盘上的文件拷贝到内存中,便可以保存、使用上次的数据,这就做到了数据的持久化。
接下来我们就来了解一下文件的相关操作。
目录
一、文件的分类
在程序设计中,我们通常讨论的文件有两种文件:1.程序文件 2.数据文件。
1.1程序文件
包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(后缀为.exe)。
1.2数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或输出内容的文件。
本篇博客我们主要讨论的是数据文件,.c程序可以从数据文件中到读取数据。
在以往的学习中,我们处理数据的输入输出都是以终端为对象,即从终端的键盘输入数据,运行结果到显示器上。
在学习了文件操作后,我们便可以使用一些文件操作来使信息输入到磁盘上、需要输出信息时便可以将磁盘上的数据读取到内存中使用。这里我们处理的就是磁盘上的一些数据文件。
二、文件的打开与关闭
2.1 文件指针
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放这个文件的相关信息(如文件的名字,文件状态和文件当前的位置等),这些信息是会存放到一个结构体变量中的,这个结构体是有系统的命名的,取名叫FILE。
接下来我们看看FILE型的结构体中对文件类型的声明吧。
不同的编译器这里FILE类型可能会不同,但是作为使用者我们不必关注太多。
为了更好的维护这个结构体变量,我们通常使用一个FILE型的指针来维护。
例如: FILE* pf;
这样pf就可以指向某个文件的文件信息区,我们就可以使用pf访方便地访问该文件。
2.2 文件的打开与关闭
既然在C语言中,每当打开文件时会创建一个FILE型的结构体变量,那么我们在打开这个文件时用一个FILE*指针指向这个结构体变量,就可以很方便的访问这个结构体变量了。
接下来我们来看看如何在C语言中打开和关闭一个文件吧。
C语言标准规定了,使用fopen函数打开文件,fcolse函数关闭文件。
fopen
这里再啰嗦几句。
fopen打开一个文件时,内存中会开辟一块空间用来创建关于这个文件的文件信息区,打开成功后fopen会返回这个文件信息区的地址,如果打开失败,fopen会返回一个NULL,所以我们再打开文件时要注意判断文件是否打开成功。
fcolse
2.3文件的打开方式
在上面中fopen的介绍中,第二个参数为打开方式,接下来我们来了解一下文件的几种打开方式。
这里我们重点了解前三个打开方式就可以了,后面的一些打开方式一般不会用到。
"w"(只读)的方式打开文件时,会将原文件的内容销毁,如果没有原文件,则会主动创建一个文件。
这里我们来看看如何打开和关闭文件。
这里我在text.c的目录下创建了一个text.txt的文本文件,我们使用只读的形式打开,如果没有找到该文件则会打开失败,所以我们要对fopen的返回值进行判空操作。
接下来我们将text.txt删除,看看会出现什么。
三、文件的顺序读写
在上面我们已经可以成功打开、关闭文件了,接下来我们来了解最重要的写文件。
3.1 stream——流
这里会看到有第三列,适用于所有XX流,那什么是流呢?
流,是一种高度的封装,我们可以将数据从程序(内存)输出到流中,也可以从流中输入数据到程序(内存)中。比如我们使用scanf函数读取数据,即是从标准输入流(键盘)中获取数据传入我们的程序(内存)中的。
在C语言程序运行时,会默认打开标准输入流(stdin)(键盘)、标准输出流(stdout)(屏幕)、标准错误流(stderr)(屏幕),这三个流也是文件指针类型。而我们现在操作的文件,也是一种文件流。所以我们使用文件操作函数可以将数据从程序中输出到任何流中或文件中,即上表中的第三列。
作为一个程序员,我们只需要将各种数据写入到流中就行了,关于流的内部我们现在可以不用关心。
3.2 fputc
这里第一个参数c即表示想输出到流中的字符的ASCII码值,stream就是我们所说的流,这里我们是将数据传入到文件中去,所以我们可以放入一个文件指针,就可以将该字符传入这个文件中了。
我们来看看如何使用写文件操作将a—z的数据录入text.txt中
为什么写文件这里是使用的是fputc而不是fgetc,因为我们是将程序中的数据放到文件中,将程序中的数据视为中心,程序的数据被输出到了外部文件中,所以这里我们要想让程序中的文件输出到文件中,要使用fputc输出到文件中去。
就好比,我们在使用时printf是将一些数据输出到控制台上,那么我们使用fputc将数据输出到文件中的道理是一样的。
接下来我们再使用fgetc将这些字符输入到程序中来,再将他们打印到屏幕上。
3.3 fgetc
fgetc读取正确会返回读到字符的ASCII码值,如果碰到文件结束(EOF)或读取错误则会返回一个EOF。
现在我们使用fgtec录入文件中的信息,再将数据打印到控制台中吧。
因为我们已经了解了这些函数可以适用于所有流,并且我们知道除了文件流还有标准输入、输出流,那我们可以在这些函数中使用标准输入、输出流来打印、录入数据。
我们只需要将字符输入到控制台上,printf和fputc就会将数据从程序(内存)中输出到屏幕中。
可以发现,这里printf和fputc同样做到了输出的效果。
3.4 fputs
这个函数的参数于fputc的参数差不多,主要的区别就在最后一个字母——操作的是字符串。
我们来演示一下直接将a—g的内容写入文件中去吧。
3.5 fgets
string表示数据的存储位置,他会将读到的数据放到你所指定的位置中去。
n表示要读取的最大字符数。这里需要注意,如果我们想读取255字符,那这个n要设置为256,因为最后一个字符要预留给'\0'。
stream就不多介绍了,上面介绍挺多的了。
这个函数会返回一个指针string,如果读取失败会返回一个NULL,我们可以利用他的返回值来判断读取是否结束。
接下来我们来演示一下如何使用。
因为fgets一次只能读取一行字符串,所以当我们记事本中有两行字符串时,fgets只会读到第一行的数据。
这里如果我们不知道文件中有几行,但是我们想全部打印出来,便可以利用fgets的返回值来打印。
3.6 fprintf
我们在上面一直向文件中输出字符或字符串的数据,但是如果我们想向文件输出一些复杂的数据,我们就可以使用格式化输出函数fprintf来输出数据。
fprintf和printf参数几乎完全一样,只是多了一个流的选项,你可以看作printf就是默认为stdout输出流的fprintf,那这样我们对于fprintf的使用就很简单了。
这里我举一个向文件中输出一个学生信息的例子。
同样,我们既然可以向文件输出,同样可以向电脑屏幕输出,只用将流该为标准输出流stdout就可以了。 这里就不做演示了。
3.7 fscanf
既然上面了解了fprintf,那fscanf也不是很难了,我们以前一直使用scanf从标准输入流(stdin—键盘)来将数据输入到程序中,那我们这使用fscanf不但可以从键盘输入数据进来,还可以从文件中输入数据到程序中来。
我们刚刚将一个学生的信息录入到了文件中,接下来我们将这个学生的信息再从文件中读取到程序中来。
3.8 sprintf/sscanf
上面介绍了格式化输入、输出函数,但是如果我们想让格式化的数据也转化为字符串的形式呢?
这里C语言也提供了两个函数,他们的作用是将格式化数据转化为字符串形式的数据,我们来了解一下。
我们依然使用上面学生的结构体变量来举例。
3.9 fwrite/ fread
这个两个函数是以二进制的形式将数据写、读到流中。
这俩函数参数相同,我就一起介绍了。
*buffer是指将数据读、写到的目标地址,可以是结构体、数组等,我们只需要将他们的地址放在buffer中就可以了。
size指读、写数据的大小,我们可以使用sizeof来计算。
count则是个数,这个就没什么好说的了。
*stream则就是流了,一般都是文件流,因为数据我们通常是在文件中操作嘛。
接下来我们举两个例子来演示他们的使用。目的是将s1的数据写入text_2.txt中。
因为是以二进制的形式写入数据,所以我们要使用wb的形式打开文件。当我们打开文件发现一些乱码的时候不要慌张,因为是以二进制的形式存放的嘛。
我们再使用fread将文件中的二进制数据输入到程序中,如下图操作。
这里我们使用fread将text_2.txt中的数据存放到s1中,记得要把打开文件的方式改为rb,因为我们这次是读数据。
这些函数现在看来可能没什么用,但是这些都是文件操作中比较常用的一些函数,在后面我们学习通讯录的过程中我们会使用到这些函数,希望大家有所了解。
四、文件的随机读写
在上面介绍的一些函数中,不知道大家有没有发现,我们并没有移动文件指针p或移动文件操作函数,但是我们读取的数据却却是有序的,不是重复的,这其实就是文件的顺序读写。
接下来我们来学习一些文件的随机读写,学习文件的随机读写可以让可以控制文件指针的指向,做到“指哪打哪”的目地,接下来让一们一起学习一下吧。
4.1 fseek
FILE*stream 这个参数通常传入文件指针。
offset 指偏移量,我们可以输入值,让文件指针偏移几个位置
origin 起始,这里提供了三个参数可以选择,控制文件的开始位置停留在哪里。
origin的三个参数:
多的不说,我们直接上例子。
这里我提前在text_3中放入数据abcdefg,然后先使用fgetc读取一个字符进入程序,再使用fputc将数据输出到标准输出(stdout—屏幕)打印出来,这里会成功将第一个字符打印出来。
因为fgetc是顺序读写,所以文件指针向后移动一位到b,我再使用fseek进行偏移,我先将第三个参数设置为SEEK_CUR(指针当前位置),然后向前偏移3个位置,成功到e的位置,再使用fputc打印出来。下面就是代码的实现。
我们如果想让指针向前偏移,可以将偏移值设置为负数。
如果我想让上面再打印一次a,就可以这样设置fseek(P,-1,SEEK_CUR)。这里就不举例了。
4.2 ftell
这个函数十分简单,他是用来告诉你文件指针当前的偏移量,相对于起始位置。
返回值为long,我们使用一个long类型的值接受并打印就行了。
我使用的是VS2019,系统中int和long的大小相同,所以可以使用%d转换说明。
4.3 rewind
让指针回到文件的起始位置。
五、文本文件和二进制文件
根据数据的组织形式,数据文件被称为文本文件或二进制文件。
数据在内存中以二进制的形式存储,如果不加转换的输出到外村,就是二进制文件。
如果要求在外存上以ASCII码的形式储存,则需要在存储前转换。以ASCII字符的形式存储的文件就是文件文本。
一个数据在内存中是怎样存储的呢?
字符一律以ASCII形式存储,数值型数据即可以用ASCII码形式存储,也可以使用二进制形式存储。
如果有整数10000,如果以ASCII码的形式输出到键盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则再键盘上只占4个字节。
将二进制文件在VS2019中打开
六、文件读取结束的判定
6.1被错误使用的feof
在文件读取过程中,不能使用feof函数的返回值直接用来判断文件的是否结束。
而是应用当文件结束时,判断是读取失败结束,还是遇到文件尾结束。
通常我们会一起使用feof和ferror函数区分读取错误和文件结束情况。
#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 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);
}
本片文章的篇幅有点长,因为主要是在介绍一些函数的使用,虽然这些函数的使用都是比较类似的,但是仍然需要我们使用、练习才能掌握。
感谢大家看到这里,希望小伙伴们可以点个赞哦,如果有错误的地方可以在评论区指出,我一定马上修改,谢谢各位,我们下次再见哦~~