前言:
今天分享的内容是和文件有关的操作。使用文件可以让我们的数据直接存存放到电脑的硬盘上,做到数据的 持久化,因此掌握对文件的操作,是一名程序员的必备技能,接下来就让我们进入今天的正题,看看到底如何对文件进行操作吧。
🥅什么是文件:
在磁盘上的文件是文件。在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能功能的角度来分类的)。
🏑程序文件:
主要包括以下几类:
- 源程序文件(后缀为.c)
- 目标文件(Windows环境后缀为.obj)
- 可执行程序文件(Windows环境下后缀为.exe)
🏑数据文件:
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者需要输出内容的文件.
今天我们谈的文件操作,都是针对数据文件来说的。在以前我们处理数据的输入输出都是已终端作为对象,即从键盘的终端输入数据,再将结果显示到终端的显示器上。有时后我们也会把信息输出到磁盘上,当需要的时候再把磁盘上的数据读取到内存中使用,这里处理的就是磁盘上的文件
🏑文件名:
一个文件要有一个唯一的标识符,一遍用户识别和引用。文件名包含以下三部分:文件路径+文件名主干+文件后缀。例如:
c:\code\test.txt
其中c:\\code\\就是文件路径,test就是文件名主干,.txt就是文件名后缀。
🥅文件的打开和关闭:
🏑文件指针:
缓冲文件系统中,关键的概念是“文件类型指针”简称文件指针。每一个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件相关的信息(如:文件的名字、文件的状态、文件当前的位置等信息)。这些信息保存在一个结构体变量中,该结构体类型是由系统声明的,取名FILE。也就是说,每当我们打开一个文件,内存中都会创建一个FILE类型的结构体变量与我们打开的文件对应。下图是VS2013中对FILE的定义。不同的C编译器的FILE类型包含的内容不完全相同,但是都大同小异。
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构体的变量,并填充里面的信息,我们不必关系里面的细节,只需要会用它就可以。一般我们都是通过一个FILE的指针来维护这个FILE结构体变量,这样使用起来更加方便。通过文件指针变量能够找到与他关联的文件
🏑fopen和fclose:
文件在读取之前要打开文件,在使用结束之后要关闭文件。在编写程序的时候,在打开文件的同时,会返回一个FILE*的指针变量指向该文件,也相当于建立了文件和指针之间的关系。ANSIC规定使用fopen函数来打开文件,fclose函数来关闭文件
⚾:fopen
FILE * fopen ( const char * filename, const char * mode );
第一个参数filename是要被打开的文件的文件名,可以是文件的绝对路径(从根目录开始,例如c:\\code\\test.txt就是一个绝对路径),也可以是文件的相对路径(相对与当前工程目录,例如:test.txt,它表示当前工程目录里的test.txt,..\\test.txt表示当前工程目录的上一级目录中的test.txt,其中..\\代表的就是上一级目录)
第二个参数mode是文件的打开方式,主要有以下几种:
文件使用方式 | 含义 | 如果指定文件不存在 |
“r”(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
“w”(只写) | 为了输出数据,打开一个文本文件,会清空文本文件中的内容 | 建立一个新文件 |
“a”(追加) | 向文本文件尾添数据 | 建立一个新文件 |
“rb”(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
“wb”(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新文件 |
“ab”(追加) | 向一个二进制文件尾添加数据 | 出错 |
“r+”(读写) | 为了读和写,打开一个文本文件 | 出错 |
“w+”(读写) | 为了读和写,建立一个新的文件夹 | 建立一个新文件 |
“a+”(读写) | 打开一个新文件,在文件尾进行读写 | 建立一个新文件 |
“rb+”(读写) | 为了读和写打开一个二进制文件 | 出错 |
“wb+”(读写) | 为了读和写,新建一个新的二进制文件 | 建立一个新文件 |
“ab+”(读写) | 打开一个二进制文件,在文件尾进行读和写 | 建立一个新文件 |
如果文件打开成功,则返回一个FILE类型的指针与打开的文件关联起来,后面就可以利用这个指针对此文件进行一定的操作,如果打开失败则返回一个空指针
⚾:fclose
int fclose ( FILE * stream );
参数是一个与要关闭的文件有关的FILE类型的指针,此函数的返回值是一个整形,当文件被成功关闭,会返回一个0,如果返回失败则会返回EOF。一般的文件打开和关闭流程,如下面的代码:
int main() { FILE* pf = fopen("D:\\code\\test.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } //读文件 //... //关闭文件 fclose(pf); pf = NULL; return 0; } //结果:fopen: No such file or directory
从打印的结果可以看出文件打开失败了,原因是:首先我们D:\\code\\这个路径下没有创建test.txt这个文件,其次我们是以“r”(只读)的方式打开的,根据上面的表格,此时如果没有对应的文件,打开时就会出错。
这里补充一个小细节,我们最好在电脑中打开显式文件扩展名,也就是我们常说的文件后缀,这样文件的名称会显示的比较齐全,避免我们在写程序打开文件时出错。
🥅文件的顺序读写:
所谓顺序读写就是从文件中的第一个字符开始往后依次读取或写入。文件的顺序读写主要涉及以下几个函数:
二进制输出 fwrite 文件
🏑输出、输入和读取、写入:
在深入了解文件顺序读写函数之前,先要明确输出、输入和读取、写入这两组概念。输出和输入都是针对内存来说的,以内存作为输出和输入的对象,输出准确来说是从内存中输出,输入准确来说是往内存中输入。而读取和写入时针对文件来说的,读取准确来说是从文件中进行读取,写入准确来说是往文件中进行写入。程序是在内存中运行的,因此要把程序运行时产生的数据存储到文件中,其实就是内存输出数据写入到文件中。而程序运行时从文件中获取数据,其实就是从文件中读取数据输入到内存当中。注意,键盘和显示器本质上也是文件。因此我们每次通过scanf从键盘上键入数据,其本质上是从键盘文件中读取数据输入到内存当中供程序运行时使用。而使用printf将程序运行的结果打印到显示器上,其本质是将程序运行时产生的数据从内存中输出写入到显示器文件当中。
总结一下:输入是和读取绑定的,输出是和写入绑定的
🏑输出流、输入流的
从上面的表格中我们可以看到,有些函数可以适用于所有的输出流或者输入流,而有的文件只适用于文件的输出流或者输入流,这里的流到底是什么意思呢?
流可以帮助我们简化输入输出操作,已输出为例,我们可能会将程序运行中产生的数据输出到不同的输出设备中(比如:屏幕、文件、网络等),针对不同的输出设备其写入方式也有所不同,作为程序员我们就需要掌握对不同输出设备的写入方式,这样才能把数据正确的写入到输出设备上,这无疑给程序员增添了许多麻烦。为了简化操作,于是就在内存和输出设备之间抽象出来了一个流的概念,此时程序员只需要知道如何把数据输出到流中去就可以,而从流中把数据写入到不同的输出设备这一过程C语言已经帮我们封装好了。流的类型是FILE*
任何一个C语言程序运行的时候会默认打开以下三个流:
stdin ---- 标准输入流(键盘)
stdout ----标准输出流(屏幕)
stderr ---- 标准错误流(屏幕)
这三个流的类型也都是FILE*,由于这三个流是在C语言程序运行的时候默认打开的,所以在一个C程序中我们可以直接使用scanf和printf。而对于一个文件,C语言程序在运行的时候不会默认打开,因此如果要对文件进行操作我们需要自己写代码来打开文件,例如:FILE* pf = fopen("test.txt", "r"); ,其本质上是获取一个文件流
所以上表中提到的适用于所有的输入流或输出流,就意味着此函数不仅可以从文件流里面获取或写入数据,以getc和putc为例:
int main() { char ret = fgetc(stdin);//从标准输入流中读取字符,输入到内存当中 fputc(ret, stdout);//从内存中输出一个字符,写入到标准的输出流里面 return 0; }
🏑fgetc:
int fgetc ( FILE * stream );
- 参数是一个文件类型的指针
- FILE结构体中有一个定位指针,指向文件中待读取的字符,最初一定是指向文件中的第一个字符
- 读取成功会返回定位指针当前所指向的字符,并且定位指针前进一位(这就是顺序读的实现原理),读取结束会返回EOF
- 因为是从文件中读取字符,因此文件一定是以读的方式打开
🏑fputc:
int fputc ( int character, FILE * stream );
- 第一个参数
character
是待写入的字符 - 第二个参数
stream
是与待写入文件所关联的文件指针(对应的文件流) - 此时是向文件中写数据,所以文件必须是以写或追加的方式进行打开
- 向定位指针当前指向的位置写入一个字符,写入成功会返回所写入的字符,并且定位指针前进以为。写入失败会返回
EOF
🏑fgets:
char * fgets ( char * str, int num, FILE * stream );
第一个参数str表示把读取到的字符串拷贝到str所指向的空间
第二个参数num表示读取num的字符,其中会包含一个’\0’,因此实际上只会读取num-1个字符
第三个参数stream是与待读取文件关联的文件指针(对应的文件流)
读取成功会返回str,读取结束会返回NULL
fgets是文本行输入函数,因此当一行没有读取完的时候,下一次读取会从上一次读取结束的位置继续读取当前行的文本。如果待读取的字符个数num大于当前文本行的字符个数,那此次读取只会把当前行的所有字符读取出来,不会去读取下一行的字符。
🏑fputs:
int fputs ( const char * str, FILE * stream );
- 第一个参数str表示待写入的字符串
- 第二个参数stream表示与待写入文件关联的文件指针
- 写入成功会返回一个非负数,写入失败会返回
EOF