你将收获:
进一步理解缓冲区,以及引申出getchar(),scanf("%c",&c)
接收数据时,易踩坑的地方,并对其解释。
w+,r+,a+
打开文件后如何正确使用读、写函数。
还有其它收获…
学完了文件操作得熟练熟练,拿通讯录试试手吧~
文件版通讯录
在以往写的程序中,运行程序后,这些数据都是在内存中,当程序运行结束,这些数据也随之消失。有什么方法可以使数据长久存在呢?一起走进文件的世界。
文件的存在的意义是让数据永久性的存储下来,当然永久性的存储不只有文件,还有数据库,数据库又是另一个高级的东西了。
根据功能分类,文件分为程序文件和数据文件。在写c语言程序时,运行前后产生文件的就是程序文件如
.c
为后缀的源程序文件,.obj
为后缀的目标文件,.exe
为后缀的,可执行程序。
数据文件:文件内容并不一定是程序,而是存放一些数据,这些数据可以被程序读和写。用一个例子来解释下,比如在写通讯录时,已经存一个文件,这个文件中存放了一些数据:张三的个人信息
在计算机语言中,读是指输入,写是指输出。
给定一个情境:在写通讯录小项目时,我需要知道文件中已经存在哪些联系人,并且呢我还需要对这些联系人的信息修改。 这里存在两个问题,怎么知道存在哪些联系人,怎么对这些数据进行修改。
首先,是不是需要将文件中的数据加载到程序中,我们才能进行下一步操作。那怎么加载就能存到程序里呢?我们只要将这些数据存到变量中,程序运行后,这些变量就会在内存中,那么数据也就完成了加载,这一过程就是读。写又怎么体现呢?已经将数据加载到程序中了,我现在想知道有哪些联系人,怎么才能知道呢,是不是要把这些数据打印出来,打印到屏幕上,我就能知道存了哪些联系人,这一过程就是写的过程。
暂且先到这里,下文我将继续以通讯录为例,让你学会这些简单的操作。
根据数据的组织形式,数据文件被称为文本文件或者二进制文件
二进制文件:打开文件是乱码的就是二进制文件,人看不懂,计算机看得懂就可以,类似于这种。
文本文件:打开文件你认识的一般都是文本文件。
二进制和文本文件浅浅的了解一下就可以了。
文件的唯一标识符也叫文件名是文件所存放的路径。
文件名包括三个部分:文件路径、文件名主干、文件后缀
相对路径和绝对路径的概念放到文件操作函数中解释,有个具体的例子会形象很多。
注意事项:
文件名并不一定要包含文件后缀。
文件名不能包含一些符号。
ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块文件缓冲区。
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。先抛出两个概念,待会连起来解释。
在程序中如何去描述文件呢?是否还记得结构体呢。在结构体这篇博客中深度讲了自定义类型。
结构体存在就是用来描述复杂对象的。文件是一个复杂对象,在C语言中也确实是用一个结构体来描述文件的,描述文件的路径、名称、状态等。不同编译器下,结构体中的成员可能不一样。
VS2019中,文件的结构体封装的很好,找不到有效的信息。
这是在VS2010中的
以上你只要了解是用一个结构体来描述文件的即可,不用太在意结构体中每个成员的含义。
我们已经知道了文件是一个结构体,那用什么去维护这个结构体呢?在创建一个?不现时,文件是通过文件指针来维护的。通过文件指针和一些文件类函数就可以对文件进行读写操作了。
缓冲文件系统这里我们要深入理解文件缓冲区
文件指针没什么好说的,就是一个指针指向的是一个结构体。文件的结构体类型为:
FILE
,文件指针的类型为FILE*
,可能你还觉得有些抽象,等下讲到函数的时候,你就知道用起来贼容易。文件信息区就是指文件结构体内部成员汇集的信息。
最主要的还是文件缓冲区,引用linux中一个句话,一切皆文件,c中把文件称流。不管是哪种语言的编译器,编译器都会默认打开三个流(文件)标准输入流,标准输出流,标准错误流,标准输入流—键盘之类的,标准输出流–显示器之类的,标准错误流–通过全局变量
errno
(记录错误码),和一些函数来控制strerror、perror
在这篇博客中有strerror、errno的相关介绍
为什么讲这个呢?不知道你是否还记得刚入门时使用
getchar(),scanf("%c",&c)
老是细节出错呢?我们可以把那三个标准流的称呼为文件,这样和下面讲的刚好连起来了。
既然标准输入流是一个文件,那是不是需要一个文件缓冲区呢?当然需要,这个缓冲区叫输入缓冲区。我们在键盘上的输入的数据都是先存放在输入缓冲区中的,当缓冲区存满了,或者你按了回车,或者其它条件,都可以触发一次输送。怎么个输送法呢?就是将输入缓冲区中的内容存到内存中去,即存到变量中。
下面浅浅的举一个例子:目的是输入两个数,一个是整形一个是字符型,在输出到屏幕上。但是在键盘上按了123回车,程序就结束了。
要弄明白这些,你还得知道
%c
输入,和%s
输入的不同。%c
接收字符,也包括'n'
(回车),%s
是不会接收'n'
和空格的。
这只是一个简单的例子,如果在平时不注重细节的话,可能你都不知道出什么错。
你可以想象一个场景:你当老师,一个学生每隔一分钟就会来问你一个问题。这时候你就不能处理其他人的问题,只能处理这个学生的问题,是不是效率极低呢?你就会对这个学生说让它过30分钟来问一次,这样你有空间的时间处理其它的事情了。
在计算机中也是如此。当文件系统接收到
read
或者write
的请求时,会向磁盘驱动程序发出指令,磁盘驱动程序接收了指令,又会向硬盘发出指令,在通过一系列的操作,就完成了一次读或者写。如果没有缓冲区存数据,一个数据一个数据的接收,一个数据一个数据的传送,你可以想象这个过程是有多么的繁琐,造成的后果就是效率极低。文件缓冲区的存在就非常有必要了。
打开文件关闭文件,和喝饮料一样,需要喝,要打开瓶盖,不喝了就把瓶盖盖上。
打开、关闭文件需要配套使用,至于具体原因大体是这样的:还记得刚刚说的文件缓冲区吗,向文件读写数据都是先存到缓冲区中,触发条件后会输送一次,这个关闭文件的操作,也是一个触发条件。有时候可能你少了关闭文件的操作,数据可能会丢失。
返回值:返回一个文件指针。
参数1:文件名
参数2:打开方式
如果打开文件失败会返回一个空指针,因此一般都需要进行一个判断,打开文件是否成功。
FILE* p =fopen(","w");
if(NULL == p)
{perror("fopen::");//错误提示return 1;
}
FILE* p1 =fopen("D:\Ccode\","w");
if(NULL == p1)
{perror("fopen::");//错误提示return 1;
}
通过文件指针
p
来维护<文件,通过p1
维护<文件。
绝对路径和相对路径在这就能很好的理解了。
相对路径是指以当前文件资源所在的目录为参照基础,链接到目标文件资源(或文件夹)的路径。
绝对路径指带域名的文件的完整路径。
以
"w"
的方式去打开文件如果文件不存在,那么会生成一个新文件,如果以"r"
的方式去打开文件,文件不存在会报错。下面有张图已经全部汇总了。
新的文件生成在哪呢?根据你在函数中写的文件名来看。p
指针,指向的文件是在相对路径下的,这个参照文件是指你正在写代码的这个文件(test.c
)的路径下新建一个文件
p1
指向的文件是绝对路径的,从磁盘到具体的文件夹下。至于为什么要\
两个,因为是一个转义字符,要让它普通,得再来个转义字符
,颇有点负负得正的意思。
一个小细节,类型都是
char*
的,是一个字符类型的指针,指向的是一个常量字符串,所以需要""
(双引号)。
运行程序后
打开方式:
当然还没结束,这只是开始,需要结合读写函数,才能更深入理解读,写,读写,这些是什么意思,稍后还会深入讲。
参数:文件指针。传一个文件指针,关闭的是这个文件指针所指向的文件。
fclose(p);
fclose(p1);
通过打开和关闭文件的操作,相信你应该可以浅浅的了解了如何通过指针来管理文件,再看几个读写函数的例子,你就能明白了。
所有输入输出流,也包括标准输入流(
stdin
)、标准输出流(stdout
)标准输入流指从键盘输入,标准输出流是指打印到屏幕上。stdin、stdout
类型是文件指针FILE*
另外要说明一点的是函数中给出的是
stream
,这个是流的英文,为了方便理解我都会说成文件。
作用:从文件中读取一个字符读取失败返回EOF,读取成功就会返回读取到的字符。
读取失败:如果到了文件的结尾或遇到读错误,将返回EOF
通过一个变量接收这个返回值存入内存,或直接输出。
EOF
是什么?是end of file
–>文件结束标志,默认用-1
表示EOF
。
FILE* stream
:文件指针,该指针指向要被操作的文件
以这个函数为例实现标准输入
示例:对文件中的字符连续读取。
fgetc
根据上文提供的图片,可知是以"r"
的方式打开文件的。测试的时候可以先在相对路路径下创建好文件,进行读操作。如果没有文件,那么会报错。
先在文件中存了一些数据,连续读取数据,通过函数返回值来控制循环条件。
注意:文件的打开方式要和读写函数配对使用
仅仅知道这些当然还是不行的,我们还需要理解内部的机制。它为什么能够读取到每个字符呢?通过读写函数对文件进行操作的时候,会存在一个文件指针,它指向文件内部的数据位置,之前所说的文件指针是不同的。
每次调用
fget()
之后,这个文件指针会向后偏移一位,直到文件末尾。
作用:将
character
以字符的形式写到文件中,标识符位置向前移动。
文件内部的文件指针指向哪,就从哪开始写入。
返回值:发生错误返回EOF
,没有发生错误返回写入的字符的ASCII码值
int character
:要写入的字符
FILE* stream
:文件指针,该指针指向要被操作的文件
最后一句话也很好理解,标识符位置就是刚刚说的文件指针,如果这个文件指针指向向文件的起始位置
0
,写入一个字符,写到0
处,这个指针会向后偏移,偏移到1
处。
如果这个文件指针指向向文件的位置是1
处,写入一个字符,写到1
处,这个指针会向后偏移,偏移到2
处。
以这个函数为例,实现标准输出
示例:向文件中写入26个英文字母。这个时候是写,打开方式需要改变,以
w
的方式开打文件。
来看一个现象:一开始文件中是有数据的。
调试:执行完以只写的形式打开文件后,内部数据还会在吗?
不会在了。由此可以得出结论:以只写的形式去打开文件,可理解为每次都重新生成一个新的同名文件。
注意:文件的打开方式要和读写函数配对使用
还有一点需要注意的,
fgetc、fputc
操作的都是字符,以ASCll码值为对应关系。
通过上面两个例子,相信你应该能很好的理解文件指针是怎么来管理文件的了,就是这么简单。函数参数为文件指针,就可以对文件进行读写等操作。
作用:从文件中读取一行字符,存到
str
所指向的字符串中,当读取 (a-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
char* str
:str
是指针用来接收读取的字符串,更准确的说str
是数组名。
int a
:读取的个数
FILE* stream
:文件指针,该指针指向要被操作的文件
返回值:
如果读取成功,该函数返回相同的 str 参数。如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。
为什么读取
a-1
个字符就会结束呢?因为字符串的结束标志是'