1. 为什么使用文件
如果没有文件,我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失 了,等再次运行程序,是看不到上次程序的数据的,如果要将数据进行持久化的保存,我们可以使用文件。
2. 什么是文件
磁盘(硬盘)上的文件就是文件。
但是我们在程序设计中,一般分为两种文件:程序文件和数据文件。
2.1 程序文件
程序文件包括源程序文件(后缀为.c),目标文件(Windows环境为.obj),可执行程序(Windows环境为.eve)。
2.2 数据文件
文件的内容不⼀定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。
其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。
2.3 文件名
一个文件要有一个唯一的标识,那就是文件名,文件名包括由3个部分组成:文件路径+文件名主干+文件后缀,为了方便起见我们将文件标识叫文件名。
3. 二进制文件和文本文件
根据数据的组织形式,数据文件被分为二进制文件和文本文件。
数据在内存中以二进制的形式存储,如果不加转换的输出到外存的文件中,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
一个数据在内存中怎么储存的呢?
字符⼀律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
4. 文件的打开和关闭
4.1 流和标准流
4.1.1 流
我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出操作各不相同,为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。
C语言程序对于文件,画面,键盘等数据都是靠流来进行数据的输入和输出的。
我们向流中写入数据或者是读取数据,都是要先打开流,再进行操作的。
4.1.2 标准流
那我们平常在从键盘输入数据和向屏幕输出数据都没有进行打开流的步骤,这是为什么?
因为我们在C程序启动的时候,会默认打开3个标准流:
4.2 文件指针
我们上面提到了FILE*是文件指针,那文件指针指的是什么呢?
用于存放文件的相关信息。
当我们每打开一个文件,系统就会根据文件的情况自动创建一个FILE结构的变量,并填入信息。
一般使用FILE的指针来维护FILE结构变量,这样我们使用时也更方便。
我们使用文件指针变量能够间接找到与它关联的文件。
4.3 文件的打开和关闭
我们在打开文件时,系统会自动创建FILE结构体(又叫开启流,也就是前面提到的会将其中的信息存入到其中),返回结构体地址给你自己的文件指针变量。
注意:我们在打开文件时,不会自己创建文件指针变量来存放结构体地址,文件指针变量由程序员自己创建,用于接收打开文件返回的FILE结构体地址。
ANSI C规定fopen函数来打开文件,fclose来关闭文件。
4.3.1 fopen
我们来试验一下怎么使用的:
这些打开的方式有很多种,r代表的是“只读”方式打开文件。我们现在只学了打开文件所以这个试验并不完整。
以上就是各种打开方式,你们记住前三个就差不多了,如果后续遇到其他的你们就直接查就行。
4.3.2 fclose
现在我们学了fclose了应该要学会关闭文件了,我们现在将其补充:
我们在关闭文件之后也一定要记得将文件指针赋值为空指针。
5. 文件的顺序读写
我们前面对文件的打开方式分了很多种方式,那我们对文件进行读写的时候,又会涉及一些函数:
我下面进行一一讲解:
5.1 fgetc
我们来实验一下就会了:
我们在进行读取之前,前提是必须要有这个文件才行,
fgetc的读取顺序是默认从第一个字符开始往后读,并不是随机读取。
5.2 fputc
我们来试验一下:
执行成功之后你会发现文件中的内容改变了,同时也有输出结果:
这是因为我们将ch还输出到stdout标准流中,也就是直接输出到屏幕上
在写入的时候,遇到\0就停止了,先写入he,第二句再写入world。
5.3 feof和ferror
我们先来测试一下feof:
我再文件中放的是abcdef,在字符打印完之后,继续打印就会显示遇到末尾了,如果没有遇到末尾会在标准流上打印字符,切记关闭文件之后,要记得进行空指针的赋值。
我们再来测试一下ferror:
上面我们也用到了ferror但是并没有发生读写错误,所以并没有使用上。
、
我们以写的形式打开文件,然后读取文件,就会发生报错,ferror(fp)等于0,执行语句,这里的运行结果出现了问号,是因为控制台编码并不兼容导致,还是运行了语句的。
perror和ferror都是可以检测错误的,而且只有一个字母的差距,我们可以进行相关记忆。
5.4 fputs
这个函数和前面我们刚学的fputc长相相似,其根本差异就是一个是将字符写入,一个是将字符串写入,我们在打印字符和字符串的时候刚好也是一个用c,一个用s,知道了这两个函数的关联还是很好记忆的。
试验:
fputs遇到\0就结束,这就导致我们运行之后就应该是heworld,我们看一下文件内容
也确实如此。
5.5 fgets
我们既然有fputs那也确实有fgets。
就是从stream中读取num个字符放入到str指向的空间中。
不是读取5个字符吗?除去\0应该还要读取4个才对,为什么全部读取出来了?这是因为我们这里有个循环,这个循环会反复适应fgets,一直往下读,一次读取4个,末尾会自动补\0,num控制单次最多读取字符个数。
如果指向读取前5个字符,就不使用while循环,调用一次就行。
5.6 fprintf
前面的输入和输出都是字符串,但是数据类型并非只有字符和字符串,fprintf和printf格式符完全是一样的,区别就是printf是打印到屏幕上,fprintf是把格式化文本写入文件中。
试验1:
试验2:
fprintf的参数部分第一个参数是文件指针,第二个参数是格式化字符串,第三个参数是可变参数部分,这其中的后两个参数其实就是printf的两个参数,就是增加了一个文件指针。
5.7 fscanf
试验:
我们上次使用fprintf后,我们已经向文件里面写入了数据,我们现在要将数据提取出来,不提取出来的话,直接使用fprintf是没有数据的。
所以我们要将文件中的数据先提取出来才能再次适用fprintf写入到其他地方去。
这一对函数都是关联的,就好比我们之前的scanf和printf,我们可以结合记忆。
5.8 fwrite
试验:
5.9 fread
试验:
将文件中的数据提取读取出来,rb的以二进制读取文件(只读)。上面的wb是以二进制写入文件(只写)。
6. 文件的随机读写
文件既然有顺序读写,自然也就有随机读写。
6.1 fseek
这三个标识我们一定要记得。
试验:文件内容是abcdefg
这里为什么是使用rb的形式,而不使用r的形式?
我们做随机读写时还是建议使用rb/wb来打开文件。
6.2 ftell
返回现在的相对偏移量,就是用于获取当前文件指针的位置。
这个程序可以算文件字节总数,偏移量(字节数)。
文件中出现汉字是占2个字节。
6.3 rewind
试验:文件内容abcdefg
这其中有疑惑的应该是这个当前位置,向前跳2个字节不应该是2吗?这是因为fread读取完之后,会自动跳过读过的字符,我们输出c之后就向后跳了一个位置。
7. 总结
本文的函数较多,但是有很多功能相似,函数形态相似的,我们可以关联记忆,减轻负担,本文让我们从代码程序走到文件,这是一个漫长的过程,从流到文件的打开和关闭,再到文件的读写,这都是需要我们一步一步来的,内容较多,篇幅过长,还请细细体会。