前面学的东西感觉都跟写代码有关系,怎么突然就开始说文件了,有什么用呢?
其实,文件是另一种数据存储的方式,学会使用文件就可以让我们的数据持久的保存。
一、文件是什么
就算没有学过相关的知识,在这么多年不管是用电脑玩游戏,还是是学习的过程中,其实都绕不开文件:
.doc结尾的word文档,.xlsx结尾的excel文档,.txt结尾的文本文件等等。
就算上面这些都没见过,写C语言的时候,创建的.c后缀的文件,也是文件,我们就算不知道是什么,至少见过这几种。
文件一般存储在电脑的硬盘上,比如网上买的那种移动硬盘了,固态硬盘了,这里面存储的数据不管是开机关机或者断电,数据都不会丢失。
一般在程序设计中,可以分为程序文件和数据文件。
1.程序文件
程序文件一般就是我们写的源文件了(.c),或者目标文件了(windows环境后缀.obj),亦或是可执行程序(.exe)。
2.数据文件
这类文件你要让他运行起来基本就不大可能,这类文件只是存储某些数据来的,也就相当于什么呢,手机里的通讯录,备忘录,这里面的玩意纯纯是人写进去的(这个相当于往文件里写数据),并且一般也不动,最多你记录个账号密码啥的有时候就会去看看(这个就可以说是调用文件中的数据)。
之前我们的输入输出都是即时的终端进行输入输出的,说人话就是,通过键盘这个终端输入,打印到屏幕这个终端去显示。
而今天我们要学习的一系列文件有关的知识和库函数,都是针对数据文件来的。
3.文件名
一般我们说的文件名就是文件主干加后缀,如:test.c data.txt等等
但是文件名往往不会单独使用,一般要带上文件的路径:
如:c:\code\test.txt
通过路径,就方便对文件进行相关操作。
二、文件数据存储形式
我们知道,在计算机最底层只能存二进制位0和1,内存中就是以二进制的形式存储的,但是如果存到我们的外存,也就是相关文件中,可以根本不动的存入二进制位,也可以以ASCII码的形式存储。
根据在文件中的存储形式的不同可以分为:二进制文件和文本文件。
光说可能挺抽象的,画个图:
这就是区别,后面介绍库函数的时候就明白了。
三、文件的打开和关闭
一看见这,就会有人说,那还不简单,双击打开,点×就关闭。
当然是让你在程序里实现文件的打开和关闭了,因为你后续不管要对文件的数据改写了,增补了,第一步都得是打开文件,用完得关闭文件。
一直有一个冷笑话不就是这样的嘛,把一个大象放冰箱需要三步,打开冰箱,放入大象,关上冰箱。
1.流和标准流
在正式开始学习对文件操作的一系列函数前,补充学习一下什么叫做流。
流
我们写程序的时候可能想要往外部设备(屏幕、网络、文件等)中去输出数据,也可能从外部设备(键盘、网络、文件等)读取数据,不同的外部设备的输入输出的操作是不尽相同的,如果程序员对于每一种设备的交互都得一点一点掌握的话实在是太麻烦了,所以为了方便程序员的操作,就抽象出来了一个流的概念。
就类似于这种。
我们创建好一个C语言文件以后,其他的事什么都不用管,只需要知道怎么与流交互就行(比如我们之前学习的格式化输入输出函数:printf函数,scanf函数)。至于流和外部设备怎么交互,那就不是我们需要在意的了。
标准流
这时候就有人要问了,你说我们是跟流进行交互的,但是写的时候之前也就是用用printf函数和scanf函数,输入的时候感觉就是键盘,输出的时候就是屏幕。
那是因为C语言在开启的时候默认打开了三个流:
stdin - 标准输入流。一般都是键盘输入,scanf函数就是从标准输入流读取数据。
stdout - 标准输出流。一般都是屏幕上输出,printf函数就是输出到标准输出流。
stderr - 标准错物流。一般就输出到屏幕上。
stdin stdout stderr的类型是FILE*,一般就称为文件指针,在C语言就是通过文件指针来对流进行维护的。
2.文件指针
对于文件指针太底层的东西也没有必要去学习,比如文件指针存的是什么、系统怎么使用这些指针等,因为文件在使用的时候系统就会自动创建一个相应的文件指针,便于对文件的后续操作。
文件在使用的时候会在内存中开辟一块文件信息区,这块空间就是用来存放文件的数据,文件指针操作的也就是这样一块空间,通过对文件信息区对文件进行相关操作,即通过文件指针间接访问文件。
3.文件的打开和关闭函数
今天介绍的所有与文件操作相关的文件都在<stdio.h>这个文件中。
fopen函数
第一个参数是const char* filename,即文件名(可以包含路径),第二个参数是以什么样的方式去打开文件:
就讲这几个方式,去打开文件
如果文件打开成功,返回一个FILE*的指针,失败则返回NULL。
例:
首先是在这个.c文件的路径下面不存在test.txt的文件,这个时候以读的形式打开文件,肯定只能失败:
创建一个再读肯定就没问题了。
fclose函数
文件有开就得有关,这样看来fopen和fclose就跟malloc(或者其他开辟动态内存的函数)和free的关系一样了。
参数也基本一致
传给我一个你上面打开的文件的指针,我就给你关了。
关成功返回0,否则返回EOF。
有借有还,有开有关。
四、文件顺序读写
接下来一对一对的介绍,因为这些两两成对的函数,参数基本相同,用法只有输入输出的区别而已。
1.fgetc和fputc函数
总得来说,这俩都是对流里面的字符进行操作,fgetc是从流里面得到字符,也就是从文件到程序,这个函数可以看作输入,fputc是往流里面写字符,也就是输出,所以为了有的读,就先往文件里写,再从文件里面读。
fputc
fputc第一个参数就是字符,因为用的时候肯定读的是ASCII码,所以是int类型变量来接收,第二个参数就是要操作的文件对应的一个FILE*的指针。
要写东西,就以写的形式打开,写前并没有这个文件,没有的话这种情况下会创建一个“test.txt”的文件,并根据循环内容,打印所有小写字母:
这里就可以看出来一点点门道,如果你要往里面写字符,FILE*肯定包含一个char*类型的指针,专门用来赋值,且char*每次赋值以后必定后移,不然就不可能出现一行都打印,最终只能展示出来个z,而不是这一串小写字母。
fgetc
进过fputc我们往文件里写了那么多东西,拿出来几个看看,参数只有FILE*的指针(操作哪个文件给他就行,不用深究),返回读取到的字符的ASCII码:
循环了五次,读的刚好就是abcde。
2.fputs和fgets函数
fputs
类比下来就是,我这里要输出字符串(再次强调,站在程序的角度往文件里写字符串就是在输出,类似于利用程序往屏幕上打印值),什么样的字符串呢?
从你给的字符串的首地址开始,一直到'\0'前,即'\0'不被包括在内。
FILE*的指针不用再解释。
实例:
还是本来没有这个文件,写的形式打开,创建以后往里写东西。
结果挺相似的,只不过这次写的是字符串。
注意,fputs可不会给你打空白字符(空格 换行等):
所以如果想写一行一行的还得手动换行。
fgets
有点细微的差别
翻译翻译就是你得给我个地方(类似于格式化输入一个值,需要给个某个变量的地址来存),一块str指向的可以存字符的内存空间,num是你期望str中到时候存几个字符(如果你想读Hello world的Hello,你看着这个是5个字符,但是存到里面应该是'Hello\0',所以读的时候就得给6,给'\0'留一个位置),FILE*不再解释。
当然,读取过程中碰见换行符或文件末尾停止读取(\n也会被读取)
读取成功返回str,读到文件末尾设置EOF标识符并返回NULL,读取错误设置ferror并返回NULL。
因为只会读num - 1个,第num个放'\0'。
如果给的num是6,刚好读完Hello;如果给的num是8,由于空格也被读,w刚好是第七个,所以输入8才能到。
3.fprintf和fscanf函数
格式化的输入输出函数,只不过是针对文件的。
这俩其实说的必要都没有:
就加了一个FILE*的指针做参数,其他完全一样,完全可以当成printf和scanf,只不过最后别忘在最前面加要操作的文件对应的指针即可。
直接一起展示了,不多bb:
以上这些讲的文件操作函数适用于所有流,剩下那俩函数只适用于文件流,但由于今天演示全部以文件形式,所以看不出来什么区别。
4.fwrite和fread函数
这两个函数都是以二进制的形式读写文件。
参数返回值都差不多,拿过来一起讲了。
void* ptr是因为要对ptr指向的那块内存空间的内容进行改写或复制,但不知道这个指针指向的空间存的什么类型的值,因此用void*来接收;
size_t的size是每读写一次读多少个字节;
size_t的count读写的次数;
FILE*这个指针不再解释。
最后返回一个成功读写的次数。
没啥可说的,参数怎么写,咱就怎么用。
五、文件随机读写
反正是学函数,不多bb
1.fseek函数
依旧参数和返回值\.
FILE*这个指针还是不用多说,用哪个文件传哪个指针;
long int的offset,其实offset我们早有了解,在前面学习的时候用过一个宏叫offsetof,当时用这个去算结构体成员变量相对于结构体起始位置的偏移量,这里的offset也是偏移量的意思,相对于谁的偏移量呢?
int的origin是一个规定好的常量,作为offset的参照物:
分别是指针起始位置、指针当前位置和文件末尾。
返回值基本没啥用。
怎么用呢?
比如我们学fgetc的时候创建的那个test.txt的文件,假如我想从里面读取ad该怎么读?
如果只是顺着读,那么必定读成abcd,因为在上面的时候验证过指针每访问一个元素后就会自动后移,如果我们自己来移动指针的话就可以实现随机读。
fseek函数就是来移动遍历文件的指针的。
提供这三种参照物就是为了方便最快找出偏移量去访问,很明显,在这里就是前两种方便,使用的时候择优。
2.ftell函数
给出文件指针即可。
算出来的是距离SEEK_SET的偏移量。
3.rewind函数
也没啥可说的,就把文件指针给他,他就给你初始化到SEEK_SET那个位置。
六、文件读取结束的判定
有两个函数是用来判断文件是因为什么原因结束的:
feof和ferror
这两个函数基本意思是完全相同的:
在读取结束以后,会有指示符。
上面讲fgetc函数的时候说了,总会有文件读取结束的时候,如果读取结束是因为碰见了文件末尾,那就设置个指示符,feof这个时候再通过文件指针去找,如果找到了这个指示符那就返回一个非0值,否则返回0;ferror同理,如果因为读取发生错误结束,也设置个指示符,ferror如果碰见了这个标识符,就会返回一个非0值,否则返回0。
这个就不再举例了。