一、文件的基础知识
1、什么是文件
在日常生活中我们所说的文件就是电脑C盘、D盘上的各种文件;但是在程序设计中,我们一般会从文件的功能的角度把文件分为两种:程序文件和数据文件。
程序文件:包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境 后缀为.exe)。
数据文件:文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件, 或者输出内容的文件。在本节中我们学习的就是数据文件
2、什么是文件名
文件名是一个文件的唯一标识,用户可以据此来对不同文件进行识别和引用。文件名包括三个部分:文件路径+文件名主干+文件后缀,例如:c:\code\test.txt,为了方便起见,文件标识常被称为文件名。
3、为什么要使用文件
我们前面学习了结构体和动态内存管理的相关知识,假设我们现在要利用这些知识要写一个通讯录的小程序,当通讯录运行起来的时候,可以给通讯录中增加、删除数据,此时数据是存放在内存中,当程序退出的时候,通讯录中的数据自然就不存在了,等下次再次运行通讯录程序的时候,数据又得重新录入,如果使用这样的通讯录就很难受。
我们在想既然是通讯录就应该把信息记录下来,只有我们自己选择删除数据的时候,数据才不复存在。 这就涉及到了数据持久化的问题,我们一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据库等方式。
使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化。
4、文件的打开和关闭
文件指针
每个被使用的文件都会在内存中开辟一个对应的文件信息区,用来存放文件的相关信息(如文件的名字,状态及当前的位置等);这些信息被保存在一个结构体变量中,该结构体类型被系统声明为FILE;不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。例如,VS2013编译环境提供的 stdio.h 头文件中有以下的文件类型申明:
struct _iobuf { //存放文件信息的结构体 char *_ptr; int _cnt; char *_base; int _flag; int _file; int _charbuf; int _bufsiz; char *_tmpfname; }; typedef struct _iobuf FILE; //该结构体被重命名为FILE
每当我们打开一个文件的时候,系统会根据文件的相关信息自动创建一个FILE类型的结构体变量,并填充其中的信息, 使用者不必关心细节;同时,这个FILE类型的结构体变量一般都是通过一个FILE的指针来维护的,这样使得其使用更加方便;在缓冲文件系统中,关键的概念是 “文件类型指针”,简称 “文件指针“。
FILE* pf; //文件指针变量
pf 是一个指向FILE类型数据的指针变量,它可以使 pf 指向某个文件的文件信息区(本质上是一个结构体变量),通过该文件信息区中的信息就能够访问该文件;也就是说,通过文件指针变量能够找到与它相关联的文件。
例如:
文件的打开和关闭
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件;ANSIC 规定使用 fopen 函数来打开文件,fclose 函数来关闭文件;在编写程序的时候,我们在打开文件的同时,fopen 函数会返回一个 指向该文件的 FILE* 的指针变量,从而建立指针和文件的关系。
//打开文件 FILE * fopen ( const char * filename, const char * mode ); # filename:文件名(可以是绝对路径或者相对路径) # mode:文件打开的模式("r"/"w"/"a"/"rb"/"wb".....) //关闭文件 int fclose ( FILE * stream ); # stream:指向文件结构的指针
详细的打开文件的方式有如下几种:
具体案例如下:
#include <stdio.h> int main() { //以只读形式打开文件 FILE* pf = fopen("test.txt", "w"); //打开失败,打印错误信息并返回 if (pf == NULL) { perror("fopen"); return 1; } //打开成功,使用 fputs("file open example", pf); //使用完毕,关闭 fclose(pf); pf = NULL; return 0; }
我们以只读的形式打开,由于我代码路径下没有test.txt这个文件,所以它会先创建一个test.txt文件,然后再把file open example这句话写入到到test.txt中。
二、文件的顺序读写
1、顺序读写相关函数
C语言中关于文件读写操作有如下函数:
2、文件读写以及流的概念
什么是文件的读与写
我们知道,我们在程序中产生的数据都是存储在内存中的,因为只要是数据,就需要占用空间,而程序设计中的空间全部由内存分配;而文件是存在于硬盘中的;同时,对于我们程序员来说,我们是编写代码的人,即我们是以内存的视角来看待文件中的数据的,所以对我们来说:文件的写代表着把程序中产生的数据写入到硬盘的文件中去,进行的是输出操作,依赖的上面表格中的有关函数是 fputc、fputs、fprintf、fwrite;文件的读代表着把硬盘中文件的数据读入到程序中来,进行的是输入操作,依赖的上面表格中的有关函数是 fgetc、fgets、fscanf、fread;
什么是 stream(流)
在上面我们提到了把程序中的数据输出到硬盘中文件中去,其实除了输出到硬盘,我们还可以把数据输出到屏幕、U盘、软盘、网络、光盘等等地方,这些被统称为外部设备,不同外部设备读与写的方式可能是不一样的,那么对我们程序员来说,如果要把每一种外部设备读与写的方式都掌握的话太过于复杂了,所以C语言在读写数据的中间封装了一层叫流的东西,数据会先被写入到流,再被写入各种外部设备中,我们程序员只需要关注如何与流进行数据交互就行了,从流到外部设备这一过程C语言底层会自动帮我们实现。
3、fgetc 与 fputc
fgetc:从文件中读取一个字符到内存中;fputc:从内存中输出一个字符到文件中。
函数参数
int fgetc( FILE *stream ); # stream 对应文件指针 # int 函数返回值,读取成功时返回对应字符,读取失败或者遇见文件末尾时返回EOF int fputc( int c, FILE *stream ); # c 要操作的字符 # stream 对应文件指针 # int 函数返回值,返回输出的字符
函数使用
int main() { //打开文件 FILE* pf = fopen("test.txt", "w"); if (pf == NULL) { perror("fopen"); return 1; } //向文件中写入字符 fputc('a', pf); fputc('b', pf); //关闭文件 fclose(pf); pf = NULL; return 0; }
int main() { //打开文件 FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } //从文件中读取字符 int ch = fgetc(pf); printf("%c\n", ch); ch = fgetc(pf); printf("%c\n", ch); //关闭文件 fclose(pf); pf = NULL; return 0; }
注意事项
这里有一个细节,那就是不管我们是读还是写,我们操作的下一个字符总是在前一个字符的后面,大家可能觉得这十分自然,其实从底层来看的话这是因为我们每次调用文件顺序读写的相关函数之后,管理该文件的文件指针都会自动向后偏移一位。
4、fgets 与 fputs
函数功能
fgets:从文件中读取一行字符到内存中;fputs:从内存中输出一行字符到文件中。
函数参数
char *fgets( char *string, int n, FILE *stream ); # string 数据的存储位置 # n 要读取的最大字符数 # stream 对应文件指针 # char* 函数返回值,读取成功返回字符串首字符的地址,失败或者遇到文件末尾返回NULL int fputs( const char *string, FILE *stream ); # string 要输出的字符串的首地址 # stream 对应文件指针 # int 函数返回值,输出成功,返回一个非负值,否则返回EOF
函数使用
int main() { //打开文件 FILE* pf = fopen("test.txt", "w"); if (pf == NULL) { perror("fopen"); return 1; } //向文件中写入一行字符 fputs("abcdef", pf); fputs("12345", pf); //关闭文件 fclose(pf); pf = NULL; return 0; }
int main() { //打开文件 FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } //从文件中读取字符 char arr[20]; fgets(arr, 5, pf); printf("%s\n", arr); fgets(arr, 5, pf); printf("%s\n", arr); //关闭文件 fclose(pf); pf = NULL; return 0; }
注意事项
文件的读和写不能同时进行;对于同一个文件,当我们以写的形式打开时,操作系统首先会将该文件中原有的数据全部清除,然后再执行后续操作;(所以上面test.txt中原有的ab不见了)
对于 fgets 函数来说,实际从文件中读取的字符的个数会比指定的字符个数少一个,因为最后一个字符会被用于字符串的结束标志’\0’;