1.文件
1.1文件的类型
为了防止数据的丢失,我们想对数据进行持久化的保存,文件也就出现了,例如我们写的代码就会以文件的形式保存在电脑中,下次打开时就不会丢失写出来的代码。
在程序设计中,一般从文件功能的角度对文件进行分类:程序文件和数据文件。
首先是程序文件,例如我们以.c为后缀保存的文件就是程序文件,程序文件包括源程序文件,目标程序文件和可执行程序文件。
其次是数据文件,程序在运行过程中需要输入数据和输出内容到文件上,这样的文件就是数据文件。例如,我们在敲代码时把数据直接打印在屏幕上,那么数据并没有被保存起来,只能通过再次运行程序才能知道数据,但是如果把数据输出到文件上,就可以直接通过文件了解到程序运行的结果。而下面要讲到的文件操作也指数据文件。
而文件名由三个部分组成:文件路径+文件名主干+文件后缀
例如:C:\user\test.txt
C:\user\ 就是文件的路径
test 就是文件名主干
.txt 就是文件后缀
1.2二进制文件与文本文件
由于数据可以以二进制的形式直接储存在外部文件中,也可以转换成ASCII码的形式储存,数据文件也可以分为二进制文件和文本文件,以ASCII码的形式储存在硬盘中的文件就是文本文件。
字符数据一律以ASCII码的形式储存,而数值型的数据则二进制和ASCII码都可,因此在以二进制文件保存的.txt文件中,我们可以正常看到字母,但是对于数值通常只能看到乱码。
1.3文件指针
首先,需要了解流的概念
程序在运行时,需要接触到各种输入和输出操作,例如,输入可以通过键盘输入,也可以通过文件输入数据,输出可以直接在电脑屏幕上输出,也可以输出到文件中。那么就将各种数据的输入输出抽象成了流这个概念。
标准流的概念也就应运而生,
stdin-标准输入流,大多数环境中从键盘输入,scanf函数就是针对标准输入流从中读取数据。
stdout-标准输出流,大多数输出环境是输出到电脑屏幕上,printf函数就是将数据输出到标准输出流中。
stderr-标准错误流,大多数环境中输出至显示屏。
而这三个流的类型就是FILE*,也就是文件指针,C语言程序在启动时会默认打开这三个标准流,因此我们可以直接使用printf、scanf函数对标准流进行操作。但是,当我们想从文件中获取数据时,就需要自己通过文件指针打开文件进行关于流的各种操作。
文件在被使用时,系统会根据文件的情况创建一个FILE结构的变量,并填充其中的信息,文件指针就用来指向这片区域,因此,我们可以通过文件指针来操作文件。
2.文件操作
2.1文件的打开和关闭
我们已经知道,可以通过文件指针来指向打开文件开辟的空间,那么我们可以通过以下方式打开文件
FILE * fopen( const char * filename, const char * mode);//打开文件
int fclose( FILE * stream);//关闭文件
其中,打开文件有不同的模式,也就是fopen函数第二的参数的作用,下面进行简单的介绍:
mode | 含义 | 如果指定文件不存在 |
r | 为输入数据,打开一个已存在的文本文件 | 出错 |
w | 为输出数据,打开一个文本文件 | 建立一个新文件 |
a | 向文本文件末尾添加数据 | 建立一个新文件 |
rb | 为输入数据,打开一个二进制文件 | 出错 |
wb | 为输出数据,打开一个二进制文件 | 建立一个新文件 |
ab | 向二进制文件末尾添加数据 | 建立一个新文件 |
r+ | 为读和写,打开一个文本文件 | 出错 |
w+ | 为读和写,新建一个文本文件 | 建立一个新文件 |
a+ | 打开一个文本文件,在文件末尾进行读和写 | 建立一个新文件 |
rb+ | 为读和写,打开一个二进制文件 | 出错 |
wb+ | 为读和写,新建一个二进制文件 | 建立一个新文件 |
ab+ | 打开一个二进制文件,在文件末尾进行读和写 | 建立一个新文件 |
以下举一个实例帮助更好理解:
#include <stdio.h>
int main()
{
FILE* pf = fopen("test.txt","w");//打开文件
if(pf != NULL)
{
fputs("fopen example",pf);//文件操作(fputs函数后面会讲)
fclose(pf);//关闭文件
pf = NULL;//避免野指针的出现
}
return 0;
}
2.2文件的顺序读写
函数名 | 功能 | 适应范围 |
fgetc | 字符输入函数 | 所有输入流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | 文本行输入函数 | 所有输入流 |
fputs | 文本行输出函数 | 所有输出流 |
fscanf | 格式化输入函数 | 所有输入流 |
fprintf | 格式化输出函数 | 所有输出流 |
fread | 二进制输入函数 | 文件 |
fwrite | 二进制输出函数 | 文件 |
文件的顺序读写涉及到以上函数,我们取fgets函数和fputs函数为例子进行讲解:
首先是fgets函数
char * fgets ( char * str, int num, FILE * stream )
函数有三个参数,第一个是一个字符数组的指针,函数会把从文件中读取到的字符串放到该数组中,第二个是要复制的字符数,第三个是文件的文件指针 。
需要注意的是,函数会在字符串末尾放上\0,所以其实复制过来的字符数只有num-1个。
然后是fputs函数
int fputs ( const char * str, FILE * stream )
函数有两个参数,第一个是要放入文件中的字符串,第二个则是文件的文件指针
需要注意的是,函数在文件中是不会自动换行的,也就是用两次该函数,前后两个字符串会直接连在一起。
2.3文件的随机读写
有时候,我们在读写文件时并不一定是从文件开头开始的,这时候我们需要用到fseek、ftell和rewind函数。
fseek函数可以用来把文件指针移动到我们想要的地方,例如:
文件中有如下字符串:abcdefg
在我们使用一次fgetc函数以后读取到字符'a',再使用fgetc函数读取到的字符就是'b',文件指针就发生了偏移。fseek函数就可以操作文件指针,比如我们想读取字母d,由于操作过两次文件光标,此时,我们输入
fseek(FILE* stream, 1, SEEK_CUR);
然后再使用fgets函数,就能得到字母d。对于fseek函数的三个参数,第一个参数是文件指针,第二个是要指针偏移的量,第三个有三种选择,SEEK_SET是从文件开始的地方开始偏移,SEEK_CUR是从文件指针当前指向的位置开始偏移,SEEK_END是从文件结束的地方开始偏移。
有的时候我们会记不清文件指针到哪了,ftell函数就能告诉我们现在文件指针相对于起始位置的偏移量,rewind函数则是把文件指针挪到文件的启示位置。两个函数都只有一个参数,也就是文件指针。
2.4文件读取结束
我们想要判断文件读取是否结束时,不能直接使用feof函数,该函数只能判断文件读取结束的原因是否是遇到文件尾结束。
如果是文本文件,可以通过fgetc判断是否是EOF,或者fgets判断返回值是否是NULL。
如果是二进制文件,则判断返回值是否小于实际要读的个数,例如使用fread函数。
2.5文件缓冲区
在输入和输出数据时,C语言会先将数据放入文件缓冲区,然后再转移到文件中或程序变量中,因此我们需要刷新文件缓存区或文件操作结束时关闭文件,这样才能避免数据丢失等问题。