1. 为什么使用文件
不使用文件,我们所写的程序存在电脑内存中,程序结束,内存回收,数据就丢失了。再次运行程序也是看不到上次运行时的数据的,如果想要将数据进行持久化保存,就需要使用文件。
2. 文件分类(包含关系)
2.1 文件名
一个文件要有一个唯一的识别标识,以便用户识别和引用。
文件名包含三部分:文件路径+文件名主干+文件后缀。
为方便起见,文件标识常被称为文件名。
磁盘(硬盘)上的内容称为文件。在程序设计中,我们谈到的文件有两种,程序文件和数据文件。程序文件有数据文件又有文本文件和二进制文件之分。
2.2 各类文件定义
2.2.1 程序文件
程序文件是计算机程序相关文件的统称,包括源程序文件(后缀为.c .cpp其他编程语言还有.java .py)、目标文件(windows环境后缀为.obj)、可执行文件(后缀为.exe)等等。
2.2.2 数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件,也就是数据文件。根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
二进制文件:数据在内存中以二进制的形式存储,如果不加转换的输出到外存的文件中,就是二进制文件。
文本文件;如果要求在外存上以ASCI码的形式存储,则需要在存储前转换。以ASCI字符的形式存储的文件就是文本文件。
一个数据在文件中是怎么存储的呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节。
3. 文件如何打开和关闭的?
3.1 流和标准流
3.1.1 流的定义
我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输入输出操作各不相同,为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。C程序针对文件、画面、键盘等的数据输入输出操作都是通过流操作的。一般情况下,我们要想向流里写数据,或者从流中读取数据,都是要打开流,然后操作。
3.1.2 标准流
为什么我们从键盘输入数据,向屏幕上输出数据,并没有打开流呢?
那是因为C语言程序在启动的时候,默认打开了3个流:
stdin:标准输入流,在大多数的环境中从键盘输入,scanf函数就是从标准输入流中读取数据
stdout:标准输出流,大多数的环境中输出至显示器界面,printf函数就是将信息输出到标准输出流中
stderr:标准错误流,大多数环境中输出到显示器界面
这是默认打开了这三个流,我们使用scanf、printf等函数就可以直接进行输入输出操作的。stdin、stdout、stderf三个流的类型是:"FILE*,通常称为文件指针。C语言中,就是通过:FILE*的文件指针来维护流的各种操作的。
3.2 文件指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,重命名为FILE。不同编译器中FILE类型不完全相同,但大同小异。
每打开一个文件,系统会根据文件情况自动创建一个FILE结构的变量,自动填充其中的信息,并返回一个FILE*的指针指向该变量,使用者不必关心结构体内容细节。我们可以通过FILE*的指针找到对应文件的文件信息区(FILE类型的结构体),通过文件信息区就可以访问该文件。
3.3 文件的打开和关闭(fopen、fclose)
文件在读写之前应该先打开文件,使用结束之后应该关闭文件。打开文件就会自动返回一个FILE*类型的指针,建立了指针和文件的关系。ANSIC规定使用fopen函数打开文件,fclose关闭文件。
//打开文件
FILE * fopen ( const char * filename, const char * mode );//关闭文件
int fclose ( FILE * stream );
fopen第一个参数是一个字符串,内容是:文件名,第二个参数mode表示文件打开模式。
4. 文件顺序读写
4.1 各类输入输出函数图解
函数 | 功能 | 适用于 |
fgetc | 字符输入函数 | 所以输入流 |
fputc | 字符输出函数 | 所以输出流 |
fgets | 文本行输入函数 | 所以输入流 |
fputs | 文本行输出函数 | 所以输出流 |
fscanf | 格式化输入函数 | 所以输入流 |
fprintf | 格式化输出函数 | 所以输出流 |
fread | 二进制输入 | 文件输入流 |
fwrite | 二进制输出 | 文件输入流 |
4.2 各类输入输出函数介绍
注:stream指向输入(出)流的FILE对象的指针
4.2.1 fgetc
int fgetc ( FILE * stream );
从流中获取当前字符,并推进位置指示器+1
如果调用时流处于文件末尾或发生错误,返回EOF并设置流的末尾(错误)指示器
4.2.2 fputc
int fputc ( int character, FILE * stream );
将字符写入流,并推进位置指示器+1
character存放字符的整型提升,在写入时转变为无符号字符
成功写入返回写入的字符,失败返回EOF并设置错误指示器(ferror)
4.2.3 fgets
char * fgets ( char * str, int num, FILE * stream );
从流中读取字符并将其存储到str中,直到(num-1)个字符或者到达换行符或者到文件末尾,使fgets停止读取,此字符(换行符或其他字符)也会作为有效字符复制到str中,同时最后一个字符放终止空字符'\0'
成功时返回str,发生错误,返回NULL,设置错误指示器(ferror)
4.2.4 fputs
int fputs ( const char * str, FILE * stream );
将str中的字符串写入流 ,从指定的地址开始(字符串首元素地址),复制直到终止空字符'\0'为止,终止空字符不会复制到流中
成功时返回一个非负值,失败返回EOF并设置错误指示器(ferror)
4.2.5 fscanf
int fscanf ( FILE * stream, const char * format, ...(附加参数) );
与scanf函数类似,scanf是从标准输入流(stdin)中读取格式化数据而fscanf是从所以输入流(标准和文件输入流)中读取格式化数据,并根据参数格式将他们存储到附件参数指向的位置
成功后,函数返回成功填充的参数列表的项数,读取发生错误或读到文件末尾,返回EOF
4.2.6 fprintf
int fprintf ( FILE * stream, const char * format, ... );
与printf函数类似,printf是向标准输出流(stdout)中输出格式化数据而fprintf是向所有输出流(标准和文件输出流)中输出格式化数据
成功后,返回写入的字符总数,发生错误,返回负数
附:sscanf函数是从字符串中读取格式化数据。
sprintf函数是将格式化数据输出到一个字符串中。
4.2.7 fread
fread和fwrite中参数:ptr是待写入(读取)的数组的指针;size是单个元素的大小;count是元素的个数。
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
从文件输入流中读取count个大小为size的元素放在ptr数组中
返回成功写入的元素总数,如果写入元素总数与count不同,会写入错误阻止函数完成
4.2.8 fwrite
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
将ptr数组中count个大小为size的元素输出到文件输出流中
返回成功写入的元素总数,如果写入元素总数与count不同,会写入错误阻止函数完成
5. 文件随机读写
5.1 fseek
int fseek ( FILE * stream, long int offset, int origin );
offset是从origin位置偏移的字节数;
origin是源位置,有三种选择:
SEEK_SET:文件起始位置
SEEK_CUR:文件当前位置
SEEK_END:文件末尾
重新定义流位置指示器,如果成功返回0,否则返回非0值
5.2 ftell
long int ftell ( FILE * stream );
返回位置指示器相对于起始位置的偏移量,失败返回-1L(长整型的-1)并设置errno
5.3 rewind
void rewind ( FILE * stream );
让位置指示器回到文件起始位置,无返回值
7. 文件读取结束判断
7.1 feof作用
feof作用是当文件读取结束时,判断文件读取结束是不是因为遇到文件末尾(也就是检测有没有设置末尾指示器)。
不能用feof的返回值直接判断函数文件读取是否结束。
附:ferror可以判断文件读取结束是不是因为遇到错误(也就是检查有没有设置错误指示器)。
7.2 如何判断文件读取是否结束
文本文件可以判断返回值是否为EOF(fgetc)或为NULL(fgets)
二进制文件可以返回值是否小于实际要读个数
示例:文本文件
#include <stdio.h>
#include <stdlib.h>int main(void)
{int c = 0; // 注意:int,⾮char,要求处理EOFFILE* fp = fopen("test.txt", "r");if(fp == NULL) {perror("File opening failed");return;}//fgetc 当读取失败的时候或者遇到⽂件结束的时候,都会返回EOFwhile ((c = fgetc(fp)) != EOF) { putchar(c);}//判断是什么原因结束的//ferror遇到错误会返回一个非0值//feof遇到文件末尾会返回一个非0值if (ferror(fp))puts("Error when reading");else if (feof(fp))puts("End of file reached successfully");fclose(fp);
}
8. 文件缓冲区
ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。文件缓冲区的存在一定程度上让操作系统更高效。
因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件(关闭文件也是一次文件缓冲区的刷新)。如果不做,可能导致读写的问题。
fflush函数可以刷新文件缓冲区。