序言:
Hello~大家好,今天给大家带来C语言中有关文件操作的内容,本章内容可能会有点抽象,如果一次性理解不了,可以先收藏下次再看~
本章内容重点:
- 为什么使用文件?
- 什么是文件
- 文件的打开和关闭
- 文件的顺序读写
目录
为什么使用文件?
在一开始,我们通过结构体完成了一个静态通讯录
syseptember的个人博客:C语言通讯录应用程序:从设计到实现
然后我们学习了动态内存管理,实现了动态通讯录
但是改进之后仍然存在一个问题:
每次程序退出后之前存放的通讯录信息不存在了,这是因为通讯录的信息存储在内存中,每一次程序结束后,内存中的信息会被清空,想要信息不被清空,我们就需要将信息存储在文件中
使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化。
什么是文件?
计算机文件,是存储在某种长期储存设备或临时存储设备中的一段数据流,并且归属于计算机文件系统管理之下。所谓“长期储存设备”一般指磁盘、光盘、磁带等。而“短期存储设备”一般指计算机内存
程序文件
源程序文件(后缀.c等),目标文件(Windows环境下后缀为.obj),可执行程序(Windows环境下为.exe),预处理文件(.i),汇编文件(.b)
数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行时需要从中读取数据的文件
本章讨论的是数据文件 :研究如何对数据文件进行读/写
文件名
一个文件要有唯一的文件表示,以便用户识别和引用。
文件名包含3部分:文件路径;文件名主干;文件后缀
例如:c:\code\test.txt
为了方便起见,文件表示常被称为文件名
文件的打开和关闭
文件指针
当我们使用代码对文件进行操作时,每一个被使用的文件都在内存开辟了一个相应的文件信息区,用来存放文件的相关信息(文件名、文件的状态、文件当前的位置等),注意:文件信息区存放的不是文件中的内容,但是可以通过文件信息区维护该文件。文件信息区本质上是一个结构体变量。结构体类型是由系统定义的,取名FILE
在VS2013编译环境提供的stdio.h头文件中文件有以下的文件类型声明
typedef struct _iobuf { char *_ptr; int _cnt; char *_base; int _flag; int _file; int _charbuf; int _bufsiz; char *_tmpfname; }FILE;
不同的C编译器FILE类型包含的内容不完全一样,但是大同小异
每当打开一个文件时,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息
一般都是通过文件指针来维护这个FILE结构的变量,这样使用起来更加方便
下面我们可以创建一个FILE*的指针变量
FILE* pf//文件指针变量
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变 量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到并维护与它关联的文件
文件的打开和关闭
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。
- 当我们打开文件时,会自动创建一个文件信息区,此时我们可以定义一个文件指针指向创建的文件信息区,打开文件时,会返回一个FILE* 的指针指向开辟的文件信息区。这有点像动态开辟时伴随着内存中会创建一段内存空间,返回这段空间的起始地址。
- 在文件操作完之后我们应该关闭文件,释放文件信息区,这有点像动态开辟结束后需要释放掉动态开辟的空间
ANSIC 规定使用fopen函数来打开文件,fclose函数来关闭文件pFile
#inlcude <stdio.h> int main() { //打开文件 FILE* pFile = fopen("test.c", "w"); //关闭文件 fclose(pFile); return 0;
fopen
- 打开名称在参数文件名中指定的文件,并将其与一个流相关联,该流可以在以后的操作中由返回的 FILE 指针标识。
- 流上允许的操作以及这些操作的执行方式由模式参数定义。
- 如果已知返回的流不是指交互设备(请参阅 setbuf),则返回的流默认是完全缓冲的。
- 返回的指针可以通过调用 fclose 或 freopen 解除与文件的关联。所有打开的文件都会在程序正常终止时自动关闭。
- “r”:当指定文件不存在时会报错
- "w":为输出操作创建一个空文件。如果已存在同名文件,则丢弃其内容并将该文件视为新的空文件。
- "a":打开文件以在文件末尾输出。输出操作总是在文件末尾写入数据,扩展它。忽略重新定位操作(fseek、fsetpos、rewind)。如果文件不存在,则创建该文件。
注意:上述的打开模式针对文本文件,为了将文件作为二进制文件打开,“b”字符必须包含在模式字符串中。这个额外的“b”字符可以附加在字符串的末尾(从而产生以下复合模式:rb", "wb", "ab", "r+b", "w+b", "a+b") 或插入字母和混合模式的 "+" 符号之间("rb+" , "wb+", "ab+").
- 如果文件成功打开,该函数将返回一个指向 FILE 对象的指针,该对象可用于在未来的操作中识别流。 否则,返回空指针。
fclose
- 关闭与流关联的文件并取消关联
- 与该流相关联的所有内部缓冲区都与它解除关联并刷新:写入任何未写入的输出缓冲区的内容,并丢弃任何未读的输入缓冲区的内容
文件的顺序读写
在正式的介绍文件的读写时,我们需要先认识流的概念
流是什么?
计算机中可以将流理解为水流,流是一种抽象的概念,可以把流看作是一种数据的载体,通过流可以实现数据交换和传输。
输入数据相当于从水流中取水,输出数据相当于像水流中倒水
注意:这里所说的输出输出是针对内存而言,如果内存中得到了数据,我们称之为输入数据,如果内存数据给出去了,我们称之为输出数据 。
这么说可能还是有点抽象,我们具体来说:
对于下面这段代码
int main() { int a; scanf("%d", &a);// printf("%d", a); return 0; }
从流的角度来理解:
变量a存放在内存中,所以当遇见scanf时内存需要从键盘中读取数据,这个流就是我们所说的键盘,键盘是标准输入流
当遇见printf时需要将内存中的数据a输出到流中,这个流就是我们所说的屏幕,屏幕是标准输出流
现在请明白一个点,我们后续要讨论关于文件的函数名是针对内存而言的
流的分类
流可以分为三类
- 输出流
- 输入流
- 错误流
只要一个流可以从内存中接受数据,它就算是输出流,只要一个流可以像内存中传递数据,它就算是输入流。
文件既可以写,也可以读,所以文件即使输入流也是输出流。
注意:当一个C语言程序进行时,默认会打开标准输出流、标准输出流、标准错误流(scanf、pruntf默认是像这些流读取数据)
文件顺序读写的函数
- 返回指定流的内部文件位置指示器当前指向的字符。然后内部文件位置指示器前进到下一个字符。
- 如果流在调用时位于文件末尾,则该函数返回 EOF 并设置流的文件末尾指示符 (feof)。
- 如果发生读取错误,函数返回 EOF 并设置流的错误指示符 (ferror)。
- fgetc 和 getc 是等价的,除了 getc 可能在某些库中实现为宏。
- 函数的返回值设置为int类型为了适应返回失败后EOF的值
- 向流中写入一个字符并推进位置指示器。
- 字符写在流的内部位置指示器指示的位置,然后自动前进一个位置
- 成功时,返回写入的字符。
- 如果发生写入错误,则返回 EOF 并设置错误指示符 (ferror)。
- 从流中读取字符并将它们作为 C 字符串存储到 str 中,直到读取 (num-1) 个字符或到达换行符或end of file,以先发生者为准。
- 换行符使 fgets 停止读取,但函数认为它是有效字符并包含在复制到 str 的字符串中。
- 在复制到 str 的字符之后自动附加终止空字符。
- fgets 与 gets 不同点:fgets 不仅接受流参数,而且还允许指定 str 的最大大小,并在字符串中包含任何结束换行符。
- 如果在尝试读取字符时遇到文件结尾,则会设置 eof 指示符 (feof)。如果这发生在可以读取任何字符之前,则返回的指针是一个空指针(并且 str 的内容保持不变)。
- 成功输出返回指向str的指针
- 如果发生读取错误,则设置错误指示符(ferror)并返回空指针(但 str 指向的内容可能已更改)。
- 函数从str指向的位置复制字符到stream所指向的位置直到遇见了结束符'\0',结束符不会被复制到stream流里
- fputs与puts不同:fputs需要指定参数流,而且fputs不会像流中添加额外的字符,但是puts会添加一个换行符
- 如果成功输出,返回非负数
- 如果输出失败,返回EOF
- 函数的一些特性和scanf很像,这里不在赘述
- fprintf和printf类似
- 从流中读取 count 个元素的数组,每个元素的大小为 size 个字节,并将它们存储在 ptr 指定的内存块中。
- 流的位置指示符按读取的字节总数提前。
- 如果成功,读取的总字节数是 (size*count)
- 如果此数字与计数参数不同,则表明发生读取错误或读取时已到达文件末尾。在这两种情况下,都设置了正确的指示符,可以分别使用 ferror 和 feof 检查。
- 如果 size 或 count 为零,则函数返回零并且流状态和 ptr 指向的内容都保持不变。
- 从 ptr 指向的内存块到流中的当前位置写入一个包含 count 个元素的数组,每个元素的大小为 size 字节。
- 流的位置指示符按写入的字节总数提前。
- 在内部,函数将 ptr 指向的块解释为 unsigned char 类型的 (size*count) 个元素的数组,并将它们按顺序写入流,就像为每个字节调用 fputc 一样。
- 如果成功,写入的总字节数是 (size*count)
- 如果此数字与计数参数不同,则写入错误会阻止函数完成。在
- 情况下,将为流设置错误指示器 (ferror)。
- 如果 size 或 count 为零,则函数返回零并且流状态和 ptr 指向的内容都保持不变。
注意:若只读文件则请使用r/rb,若只写文件请使用w/wb
sscanf
sscanf是针对字符串格式化输入的函数(若想要输入整数,则字符串第一个非空白字符应该是整形,并且一直读取到第一份非整形字符,类似于atoi)
sprintf
sprintf是针对字符串的格式化输出函数
总结
现在来总结一下本章所涉及到的知识点:
- 使用文件可以将数据长期保存起来,使数据持久化
- 文件分为程序文件和数据文件。程序文件:源程序文件、目标文件、可执行文件。数据文件:存放程序运行时的数据的文件,比如程序需要读取数据的问价或者输出内容的文件
- 文件名分为3部分:文件路径、文件名主干、文件后缀
- 每个被使用的文件都在内存中开辟了对应的文件信息区(FILE对象),我们通过文件指针找到文件信息区,并且通过文件信息区来为对文件进行维护(读、写)
- 读写文件前应该打开文件、文件使用完后应该关闭文件,fopen打开文件、fclose关闭文件。文件有多种打开方式,当打开方式为"w"、"wb"时,只可以对文件进行写入,写入的文件如果有重名、会先销毁重名文件再写入新内容;打开方式"r"、"rb",只可以对文件进行读取,读取的文件必须存在
- 计算机中流是一种数据的载体,内存可以对流中的数据进行输入、输出
- 文件的顺序读写有多种函数