当程序的生命周期结束,在内存中存放的数据就会随着内存的释放而清除,这并不满足我们日常生活中的记录需求,所以C语言开发了文件操作模式,通过将数据存放在硬盘,数据库等方式,实现数据的持久化。
文件存在于磁盘中,按照文件功能的角度来看一般分为两类:程序文件与数据文件。
程序文件:包括源文件(.c),目标文件(windows环境下.obj)与可执行文件(windows环境下.exe)。
数据文件:文件的内容不一定是程序,而是程序运行时读写的数据,数据文件的职能为提供给程序文件数据或者报存程序文件运行所产生的数据。
文件名:文件名(文件标识)分为三部分:文件路径+文件名主干+文件后缀
例如:c:\code\test.txt 其中c:\code\ 为文件路径 test为文件名主干 .txt为文件后缀
注意:文件路径指的是从盘符到该文件所经历的路径中各符号名的集合
文件的打开和关闭
文件类型指针:简称为文件指针。每个被使用的文件都在内存中开辟了一个相应的文件信息区,该区中存放对应文件的相关信息(文件名,大小等),这些信息被保存在一个由系统自定义的结构体类型(FILE)的变量中。
不同的C编译器中FILE类型中成员变量不完全相同,但一般大同小异。
每打开(或创建)一个文件时,系统会创建相应的FILE变量并在其中填充对应信息。
对于文件操作函数来说,很多函数的类型都是FILE*,所以我们可以创建临时变量FILE* pf承接函数返回值。通过文件指针变量能找到与其对应的文件。
文件在读写之前应该先打开文件,使用之后关闭文件。
ANSIC规定使用fopen函数打开文件,fclose函数关闭文件。
FILE* fopen(const char* filename,const char* mode)
filename-文件名,mode-打开方式。在打开文件时,fopen会创建一个对应该文件的文件信息区并返回信息区的首地址,打开失败则返回NULL。如:
以读方式打开了一个名为test.txt的文件,并将此文件的指针传给临时变量pf,并检查fopen是否打开文件成功。
失败原因:1.可能没有能对应该文件名的文件。
2.有些系统会默认隐藏文件拓展名,所以命名的时候需要注意。
3.若想要打开的文件是另一储存路径下的同名的文件(打开的文件不在程序产生的文件中),应该写全需要打开的文件的路径(又叫绝对路径)。
关闭文件函数: int fclose(FILE* stream)
如上在程序最后 fclose(pf); 把文件指针pf对应的文件关闭。
再用 pf=NULL; 防野指针。
全部打开方式:
使用w(写文件)方式打开一个文件时,如果该文件之前存在并有内容,则该文件会被先销毁再创建,内容也会清空。
‘b’其实是一种描述方式,意为对文件进行二进制操作,wb表示对文件以二进制写入形式打开,rb表示对文件以二进制读入方式打开。
文件的顺序读写
函数:
字符输出函数:int fputc(int c,FILE* stream);
c可以是整型,也可以是字符(或其他整型家族的成员)。用法:
结果:在pf所指的文件中有a到z的字符串。
字符输入函数:int fgetc(FILE* stream);
返回值为读到的字符的ASCLL码值,如果文件读取完毕或者读取错误会返回EOF。用法:
从刚刚写入a到z字符的文件中从前往后读取,最终结果就为打印a到z的全部字符(即文件里的所有内容)。
注意:读取的位置会随着函数的使用次数增加自动向后移动,无序附加操作。
此处引入新概念:流:在外部设备上层创建出来,程序会通过流来读取通过外部设备产生的数据。
外部设备(键盘,屏幕,U盘,硬盘,网卡...)。
文件流:
标准输入流 stdin 键盘
标准输出流 stdout 屏幕
标准错误流 stderr 屏幕
注意:只要有一个C程序运行起来,这三个文件流会自行打开。
getchar只针对标准输入流stdin(键盘)。即使对stdin重定向,getchar针对的也只是stdin。f系列的输入输出函数都是作用于所有流的。
其他函数:
fgetc(stdin)就意味着该函数可从键盘上直接读取数据并作为函数的返回值返回。fgetc(stdin)运行时以字符为单位,从文件中读取一个字符。
fputs("wdkavbrk",pf);作用为将这个字符串写入对应文件中,不自带换行功能,若要实现换行可在字符串末尾加上转义字符'\n',如: fputs("wdkavbrk\n",pf);
char* fgets(char* string,int n,FILE* stream);将stream所指文件中的前n个字符放到string所指地址开始的n个空间中。(碰到换行符号或者终止符会提前结束)(返回值为string的首地址,读取失败返回NULL)
fprintf函数:用法:
fprintf函数相较于printf函数只多了一个FILE*类型的参数
首先在main之前创建了结构体类型,创建变量之后打开文件,用fprintf将结构体写入pf对应文件中,关闭文件,程序的结果为对应文件中有经fprintf规定的输入格式与数据。
fscanf函数:用法:
相较于scanf函数只多了一个FILE*类型的参数
将文件中的数据按照scanf中对应格式取出并送到对应变量地址中。
sprintf函数:用法:
sprintf相较于printf前多了一个char*类型的参数,作用为将printf中格式即相应数据全部转化为字符并输入到char*参数所指的地址中。(遇到换行或终止符会提前停止)
sscanf函数:
相较于scanf多了一个char*类型的参数,按格式(空格分割字符串内容)从字符串中提取数据并送到最后的变量列表中。
各函数对比:
二进制输入函数:size_t fread(const void*buffer,size_t size,size_t count,FILE* stream)返回值为完整的读取出来的元素的个数,搭配打开文件方式(rb),将文件中count个size大小的空间的数据以二进制数据的读取方式读出并输入进buffer对应地址中。
二进制输出函数:size_t fwrite(const void*buffer,size_t size,size_t count,FILE* stream)
需要搭配打开文件方式(wb)将从地址buffer向后检索,每个空间长为size,检索count个空间,并将这些空间中的内容(以二进制码的形式)输入到pf对应文件中。
文件的随机读写:
fseek函数:
因为无论是读是写还是其他对文件的操作,操作后其位置会往后移一位,所以有SEEK_CUR(即指向文件指针当前的位置)这个概念。SEEK_END-文件末尾的位置即文件最后一个字符的后一位。SEEK_SET-文件开始位置为文件的第一个字符的位置。
偏移量为目标位置减去当前位置所得值,如1表示目标位置在此位置之后1个位置,-1则表示目标位置在此位置之前一个位置。
若以w形式打开文件并写入abcd,则当前所在位置为d后面一个位置,若想要打印字符b,就可以写char ch=fseek(pf,-3,SEEK_CUR);经此操作之后文件指针当前位置指向b后面一位。
其他函数
ftell函数:long ftell(FILE* pf);返回值为当前文件指针所指位置对于文件开始位置的偏移量。
rewind函数:void rewind(FILE* stream);让文件指针的位置回到文件的起始位置
文本文件与二进制文件(根据数据的组织形式划分)
文本文件:我们能看得懂的文件。在外存上以ASCLL码的形式存储。
二进制文件:因为数据在内存中以二进制码存储,如果不转换直接将这些数据输出到外存,那这些数据不能直接看懂。
注意:字符在内存中一律按ASCLL码形式存储,数值可以使用ASCLL存储方式也可以使用二进制存储方式。
文件读取结束的判定
被错误使用的feof
feof只能用来确定结束的原因而不是结束的判定准则。
正确的判定手段为根据函数的返回值来判断。或者有些二进制函数返回的是运用字符的个数,可以通过这些字符的个数与已知文件字符的个数来判断是否结束。
int feof(FILE* stream);如果feof返回非空值,则说明文件遇到EOF正常结束。
int ferror(FILE* stream);如果ferror函数返回值非空,则说明文件因为遇到错误结束。
文件缓冲区:
ANSIC标准采用缓冲文件系统处理数据文件,该系统会在内存中为每一个正在使用的文件创建文件缓冲区,数据在内存中使用,当数据需要存到硬盘时先经过对应文件的缓冲区,缓冲区填满则将缓冲区里的数据全部送入硬盘中,从硬盘中取出数据也是如此。这种传输方式可提高操作系统的效率。缓冲区的大小根据C编译系统决定。
若想将未填满的缓冲区中内容直接输入进硬盘中可用fflush(pf);。使用fclose(pf);时也可以将剩余内容送入硬盘。通常会使用这两个函数来防止数据缺漏。