目录
一、为什么使用文件
在前面的学习中,每当我们写出一个程序后,程序运行起来时,我们所输入的数据是存放在内存中的。当程序退出时,程序中的数据就不复存在了,等下一次打开程序时,数据又需要重新输入。因此,我们可以使用文件将数据直接存储在电脑的硬盘中,只有我们选择自己删除数据时它才能不复存在,这样就实现了数据的持久化。
二、什么是文件
2.1 数据流
在描述文件之前,我们要先说明一个东西叫数据流。数据流可以将整个文件里的数据看作一串连续的字符(字节),在数据的输入和输出时,不同设备对数据的输入和输出的格式不同,因此很容易产生不兼容的问题,而用数据流就能很好的解决这个问题。
数据流借助文件指针的移动来访问数据,文件指针目前所指的位置就是要处理的数据,经过访问后的指针会继续向后移动,每个数据文件后面都有一个文件结束符号(EOF),若指针指向了(EOF),则表示数据已经访问完成。
C语言程序,只要运行起来,就默认打开了三个流:
stdin - 标准输入流 - 键盘
stdout - 标准输出流 - 屏幕
stderr - 标准错误流 - 屏幕
2.2 文件
磁盘上的文件就是文件。但在程序设计中,文件按功能一般分为两种:程序文件和数据文件。本章我们来讨论数据文件。
数据文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行时需要读写的数据文件或输出内容的文件。
2.3 文件名
文件名包括三部分:文件路径+文件名主干+文件后缀。例如:c:\code\test.txt
三、文件的打开和关闭
3.1 文件指针
每个文件被使用时,都会在内存中开辟一块空间来存放文件的相关信息,这块空间被称为文件信息区,这个其实就是一个结构体变量,名叫“FILE”,是由系统声明的。返回和操作的都是这个文件信息区的地址,因此文件指针是指向FILE这个结构体的指针变量,通过文件指针即可找存放某个文件信息的结构变量,然后按结构变量提供的信息找到该文件,实施对文件的操作。
文件指针的定义:
FILE *pf;//pf为文件指针变量
3.2 文件的打开
文件在读写之前应该打开文件。
打开文件的函数:fopen
FILE *fopen(const char *filename,const char *mode);//打开文件
fopen的返回类型是FILE*,故当用fopen打开文件时,它会主动创建文件信息区,并返回文件信息区的起始地址,相当于建立了指针和文件的关系。
打开文件时,const char *mode即字符串模式,它将指定文件请求的访问类型:
字符串模式 | 含义 | 指定文件不存在时 |
“r”(只读) | 输入数据,打开已存在的文本文件 | 出错 |
“w”(只写) | 输出数据,打开文本文件 | 建立新文件 |
"a"(追加) | 向文本文件末尾添加数据 | 建立新文件 |
"rb" | 输入数据,打开一个二进制文件 | 出错 |
"wb" | 输出数据,打开一个二进制文件 | 建立新文件 |
"ab" | 向二进制文件末尾添加数据 | 出错 |
"r+" | 读取和写入,打开文本文件 | 出错 |
"w+" | 读取和写入,打开新文件 | 建立新文件 |
"a+" | 在文件末尾读取和写入 | 建立新文件 |
"rb+" | 读取和写入,打开二进制文件 | 出错 |
"wb+" | 读取和写入,打开新的二进制文件 | 建立新文件 |
"ab+" | 在二进制文件末尾读取和写入 | 建立新文件 |
示例:
int main() {
FILE *pf = fopen("abcde","w");
return 0;
}
但是在写文件路径时,有时候单斜杠和文件名会形成转义字符从而无法找到目标,因此要把单斜杠改为双斜杠防止转义。
比如:FILE *pf = fopen("D:\2023_code\class2\test_4_8\test.dat","r");
其中“\202”,“\c”,“\t”被转义了,因此要改成:
FILE *pf = fopen("D:\\2023_code\\class2\\test_4_8\\test.dat","r");
3.3 文件的关闭
在文件使用结束后,应该关闭文件。
关闭文件后,会将FILE文件指针的相关资源及所占用的缓冲区归还给系统。
文件关闭函数:fclose
int fclose(FILE *stream);//关闭文件
示例:
int main() {
......//打开文件
fclose(pf);
pf = NULL;
return 0;
}
在程序中,一个文件使用完毕后,若采用读取模式打开文件,可以不必做关闭文件的操作;但如果是采用写入模式,则一定要关闭文件,否则放在缓冲区的数据无法写回文件,从而使数据遗失。
四、文件的顺序读写
功能 | 函数名 | 适用于 |
字符输入函数 | fgetc | 所有输入流 |
字符输出函数 | fputc | 所有输出流 |
文本行输入函数 | fgets | 所有输入流 |
文本行输出函数 | fputs | 所有输出流 |
格式化输入函数 | fscanf | 所有输入流 |
格式化输出函数 | fprintf | 所有输出流 |
二进制输入 | fread | 文件 |
二进制输入 | fwrite | 文件 |
4.1 函数fputc
int fputc(char ch,FILE *stream);
示例:
int main() {
......//打开文件
//写文件
fputc('a',pf);
fputc('b',pf);
fputc('c',pf);
......//关闭文件
}
4.2 fgetc
fgetc从标准输入流读取时,即fgetc(stdin),即键盘输入;
fgetc从文件中读取时,即文件数据输入;
4.3 fputs
fputs可以使文件按照行来写
int fputs(const char *string,FILE *stream);
4.4 fgets
char *fgets(cahr *string,int n,FILE *stream);
相当于从流里读取数据放入string里。
其中n是最多能读取的字符的个数,但实际输出的个数会少1,因为存在“\0”。
4.5 fprintf和fscanf:
scanf | 针对标准输入的格式化的输入语句 - stdin |
fscanf | 针对所有输入流的格式化的输入语句 - stdin/文件 |
sscanf | 从一个字符串中读取一个格式化的数据 |
printf | 针对标准输入的格式化输出语句 - stdout |
fprintf | 针对所有输出流的格式化输出语句 - stdout/文件 |
sprintf | 把一个格式化的数据,转换成字符串 |
4.6 二进制的读写
fwrite:从buffer中所指向的内存区开始,一次输出size个字节,重复count次,,并将输出的数据存入到fp所指的文件中,同时将读写位置指针后移size *count次。
size_t fwrite(const void *buffer,size_t size,size_t count,FILE *stream);
fread:从文件指针fp所指文件的当前位置开始,一次读入size个字节,重复count次,并将所读取到的数据存到buffer开始的内存区中,同时将读写位置指针后移size *count次。该函数的返回值是实际读取的count值。
int fread(void *buffer,size_t size,size_t count,FILE *stream);
五、文件的随机读写
5.1 fseek
其作用是根据文件指针的位置和偏移量来定位文件指针。
int fseek(FILE *stream,long int offset,int origin);
其中“offset”为偏移量,“origin”为起始位置。
int origin | 位置 |
SEEK_CUR | 文件指针的当前位置 |
SEEK_END | 文件的末尾位置 |
SEEK_SET | 文件开头的位置 |
示例:
int main() {
......//打开文件
fseek(pf,2,SEEK_CUR);//调整文件指针,使其从当前文件指针所指的位置向后指2个位置
fseek(pf,-1,SEEK_END);//使其从文件末尾向前指一个位置
......//关闭文件
}
注:当起始位置为“SEEK_END”时,即起始位置为文件末尾,偏移量只能为负数,即只能向前指。
5.2 ftell
返回文件指针相对于起始位置的偏移量,也就是告诉我们当前位置相对于起始位置的偏移量是几。
long int ftell(FILE *stream);
5.3 rewind
让文件指针的位置回到文件的起始位置。
void rewind(FILE *stream);
六、文本文件和二进制文件
根据数据的组织形式,数据文件被分为文本文件和二进制文件。数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。如果要求在外存上以ASCII码的形式存储,则需要在存储前转换,以ASCII字符的形式存储的文件就是文本文件。
七、文件读取结束的判定
7.1 feof
在文件读取过程中,不能用feof函数的返回值直接用来判断文件是否结束,feof是用于当文件读取结束时,判断是因为读取失败而结束还是因为遇到文件尾而结束。
7.2 fgetc和fgets和fread判断结束
1、文本文件的判断读取是否结束
当使用的函数是fgetc时,判断结束的条件是返回值是否为EOF。读取结束时返回EOF,正常读取时,返回读取字符的ASCII码值,
当使用的函数是fgets时,判断结束的条件是返回值是否为NULL。读取结束时返回NULL,正常读取时,返回存放字符串的空间起始地址。
2、二进制文件判断读取是否结束
当使用的函数是fread时,判断结束的条件是返回值是否小于实际要读的个数。正常读取时,返回的是实际读取到的完整元素的个数。如果发现读取到的完整的元素的个数小于指定的元素个数,就是最后一次读取。
八、文件缓冲区
系统会自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上,如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区诸葛地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C语言编译系统决定的。因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。如果不做,可能导致读写文件的问题。