一.认识文件
什么是⽂件? 磁盘(硬盘)上的⽂件是⽂件。 但是在程序设计中,我们⼀般谈的⽂件有两种:程序⽂件、数据⽂件(从⽂件功能的⻆度来分类 的)。
1.程序⽂件 :程序⽂件包括源程序⽂件(后缀为.c),⽬标⽂件(windows环境后缀为.obj),可执⾏程序(windows 环境后缀为.exe)。
2. 数据⽂件 :⽂件的内容不⼀定是程序,⽽是程序运⾏时读写的数据,⽐如程序运⾏需要从中读取数据的⽂件,或 者输出内容的⽂件。本章讨论的是数据⽂件。 在以前各章所处理数据的输⼊输出都是以终端为对象的,即从终端的键盘输⼊数据,运⾏结果显⽰到 显⽰器上。 其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使⽤,这⾥处 理的就是磁盘上⽂件。
3.⽂件名
⼀个⽂件要有⼀个唯⼀的⽂件标识,以便⽤⼾识别和引⽤。
⽂件名包含3部分:⽂件路径+⽂件名主⼲+⽂件后缀
例如: c:\code\test.txt
为了⽅便起⻅,⽂件标识常被称为⽂件名。
二. ⼆进制⽂件和⽂本⽂件?
根据数据的组织形式,数据⽂件被称为⽂本⽂件或者⼆进制⽂件。
数据在内存中以⼆进制的形式存储,如果不加转换的输出到外存的⽂件中,就是⼆进制⽂件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的⽂件就是⽂ 本⽂件。
⼀个数据在⽂件中是怎么存储的呢?
字符⼀律以ASCII形式存储,数值型数据既可以⽤ASCII形式存储,也可以使⽤⼆进制形式存储。
如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占⽤5个字节(每个字符⼀个字节),⽽ ⼆进制形式输出,则在磁盘只占四个字节。
这里以vs2013为例,打开二进制文件
三.文件的打开与关闭
1. 流和标准
①流
我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出
操作各不相同,为了⽅便程序员对各种设备进⾏⽅便的操作,我们抽象出了流的概念,我们可以把流
想象成流淌着字符的河。
C程序针对⽂件、画⾯、键盘等的数据输⼊输出操作都是通过流操作的。
⼀般情况下,我们要想向流⾥写数据,或者从流中读取数据,都是要打开流,然后操作。
②标准流
那为什么我们从键盘输⼊数据,向屏幕上输出数据,并没有打开流呢?
那是因为C语⾔程序在启动的时候,默认打开了3个流:
•stdin - 标准输⼊流,在⼤多数的环境中从键盘输⼊,scanf函数就是从标准输⼊流中读取数据。
•stdout - 标准输出流,⼤多数的环境中输出⾄显⽰器界⾯,printf函数就是将信息输出到标准输出
流中。
•stderr - 标准错误流,⼤多数环境中输出到显⽰器界⾯。
这是默认打开了这三个流,我们使⽤scanf、printf等函数就可以直接进⾏输⼊输出操作的。
stdin、stdout、stderr 三个流的类型是: FILE * ,通常称为⽂件指针。
C语⾔中,就是通过 FILE* 的⽂件指针来维护流的各种操作的。
4.2 ⽂件指针
缓冲⽂件系统中,关键的概念是“⽂件类型指针”,简称“⽂件指针”。
每个被使⽤的⽂件都在内存中开辟了⼀个相应的⽂件信息区,⽤来存放⽂件的相关信息(如⽂件的名
字,⽂件状态及⽂件当前的位置等)。这些信息是保存在⼀个结构体变量中的。该结构体类型是由系
统声明的,取名 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;
不同的C编译器的FILE类型包含的内容不完全相同,但是⼤同⼩异。
每当打开⼀个⽂件的时候,系统会根据⽂件的情况⾃动创建⼀个FILE结构的变量,并填充其中的信
息,使⽤者不必关⼼细节。
⼀般都是通过⼀个FILE的指针来维护这个FILE结构的变量,这样使⽤起来更加⽅便。
下⾯我们可以创建⼀个FILE*的指针变量:
1 FILE* pf;//⽂件指针变量
定义pf是⼀个指向FILE类型数据的指针变量。可以使pf指向某个⽂件的⽂件信息区(是⼀个结构体变量)。通过该⽂件信息区中的信息就能够访问该⽂件。也就是说,通过⽂件指针变量能够间接找到与它关联的⽂件。
3 .⽂件的打开和关闭
⽂件在读写之前应该先打开⽂件,在使⽤结束之后应该关闭⽂件。
在编写程序的时候,在打开⽂件的同时,都会返回⼀个FILE*的指针变量指向该⽂件,也相当于建⽴了 指针和⽂件的关系。
ANSI C 规定使⽤ fopen 函数来打开⽂件, fclose 来关闭⽂件。
//打开⽂件
FILE * fopen ( const char * filename, const char * mode );
//关闭⽂件
int fclose ( FILE * stream );
mode表⽰⽂件的打开模式,下⾯都是⽂件的打开模式:
四.⽂件的顺序读写
顺序读写函数介绍
1.fgetc
函数原型: int fgetc(FILE *stream);
结构组成:
- 参数: FILE *stream 用于指定从哪个文件流(如文件、标准输入流 stdin 等 )读取字符。
- 返回值:返回读取到的字符(提升为 int 类型 ),文件末尾或出错时返回 EOF 。
- 内部逻辑:从 stream 指向的输入流中逐个读取字符,每次读取一个。
2.fputc
函数原型: int fputc(int c, FILE *stream);
结构组成:
- 参数: int c 是要写入的字符(以 int 形式传递 ), FILE *stream 确定向哪个文件流(文件、标准输出流 stdout 等 )写入。
- 返回值:成功写入返回写入的字符( int 形式 ),失败返回 EOF 。
- 内部逻辑:将字符 c 写入到 stream 指向的输出流中。
3.fgets
函数原型: char *fgets(char *s, int n, FILE *stream);
结构组成:
- 参数: char *s 是用于存储读取文本行的字符数组指针; int n 规定最多读取 n - 1 个字符(留位置给 '\0' ); FILE *stream 指明从哪个输入流读取。
- 返回值:成功返回 s (字符数组指针 ),文件末尾或出错返回 NULL 。
- 内部逻辑:从 stream 指向的输入流中按行读取文本,读取到换行符或达到 n - 1 个字符时停止。
4.fputs
函数原型: int fputs(const char *s, FILE *stream);
结构组成:
- 参数: const char *s 是要写入的字符串指针, FILE *stream 用于指定输出流(文件、 stdout 等 )。
- 返回值:成功返回非负整数(一般是写入字符数 ),失败返回 EOF 。
- 内部逻辑:把字符串 s 写入到 stream 指向的输出流,不写字符串结束符 '\0' 。
5.fscanf
函数原型: int fscanf(FILE *stream, const char *format, ...);
结构组成:
- 参数: FILE *stream 确定输入流来源; const char *format 是格式控制字符串,规定读取数据格式; ... 是可变参数列表,对应格式说明符的变量地址。
- 返回值:返回成功匹配并赋值的输入项数,文件末尾或出错返回 EOF 。
- 内部逻辑:按照 format 规定的格式,从 stream 指向的输入流读取数据并存储到对应变量。
6.fprintf
函数原型: int fprintf(FILE *stream, const char *format, ...);
结构组成:
- 参数: FILE *stream 指明输出流目标; const char *format 为格式控制字符串,决定输出数据格式; ... 是可变参数列表,是要输出的数据项。
- 返回值:返回成功写入的字符数,出错返回负数。
- 内部逻辑:依据 format 格式,将可变参数列表中的数据格式化后写入 stream 指向的输出流。
7.fread
函数原型: size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
结构组成:
- 参数: void *ptr 是存储读取数据的缓冲区指针; size_t size 为每个数据块大小(字节 ); size_t nmemb 是要读取的数据块数量; FILE *stream 是输入流文件指针。
- 返回值:返回实际读取的数据块数量,小于 nmemb 可能是文件末尾或出错。
- 内部逻辑:从 stream 指向的文件输入流中,按指定大小和数量读取二进制数据到 ptr 指向的缓冲区。
8.fwrite
函数原型: size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
结构组成:
- 参数: const void *ptr 是要写入数据的缓冲区指针; size_t size 为每个数据块大小(字节 ); size_t nmemb 是要写入的数据块数量; FILE *stream 是输出流文件指针。
- 返回值:返回实际写入的数据块数量,小于 nmemb 表示写入出错。
- 内部逻辑:将 ptr 指向缓冲区中的数据,按指定大小和数量写入到 stream 指向的文件输出流。
示例:
这段 代码开头通过宏定义屏蔽了一些安全警告,随后引入必要头文件。 test 函数尝试以写入模式打开 data.txt 文件,若失败则输出错误信息并返回;成功打开后写入字符串 “abcd”,若写入失败也输出错误信息并关闭文件。 main 函数先调用 test 函数,接着以读取模式打开 data.txt ,若成功则读取最多2个字符到字符数组 pc 并逐个输出,若读取失败则输出错误信息并关闭文件。代码完整展现了文件的读写操作流程及错误处理机制 。
五.文件的随机读写
1.文件指针与位置标记
在C语言中,每个打开的文件都关联着一个文件指针( FILE * 类型) ,系统为文件内部维护着一个位置标记,指向文件中当前读写的位置。对于顺序读写,位置标记按顺序移动;而随机读写可通过函数改变位置标记,实现对文件任意位置的操作。
2.实现随机读写的函数
fseek 函数
- 功能:用于改变文件位置标记的位置。通过指定从文件开头( SEEK_SET )、当前位置( SEEK_CUR )或文件末尾( SEEK_END )开始,移动一定字节数( offset )。例如, fseek(fp, 10, SEEK_SET); 表示将文件指针 fp 指向的文件的位置标记移动到距离文件开头10个字节处。
- 适用场景:适用于二进制文件,因为文本文件在不同系统下换行符等字符存在转换,可能导致偏移量计算不准确。
ftell 函数
- 功能:用来获取文件位置标记相对于文件开头的当前位置,返回值为长整型,表示字节数。比如在文件读写过程中,可通过 ftell 函数记录当前位置,方便后续操作。
- 应用:常用于需要记录文件读写进度,或后续要回到特定位置继续读写的场景。
rewind 函数
- 功能:将文件位置标记重新定位到文件开头。它等价于 fseek(fp, 0L, SEEK_SET); ,但更简洁直观。
- 使用场景:当需要重新从文件开头进行读写操作时,可使用该函数。
3.操作流程
首先使用 fopen 函数以合适的模式(如 r+ 、 w+ 、 a+ 等)打开文件。
根据需求,使用 fseek 函数将文件位置标记移动到目标位置。
使用 fread 函数从指定位置读取数据,或使用 fwrite 函数向指定位置写入数据。
操作完成后,使用 fclose 函数关闭文件。
4.注意事项
在使用 fseek 函数时,要注意偏移量的计算,特别是在处理文本文件时,换行符等可能带来的字节数差异。
随机读写操作可能改变文件原有内容布局,要谨慎操作,避免数据丢失或文件损坏。
确保文件打开模式具有相应的读写权限,否则随机读写操作可能失败。
六. ⽂件读取结束的判定
1 .被错误使⽤的 feof
牢记:在⽂件读取过程中,不能⽤feof函数的返回值直接来判断⽂件的是否结束。
feof 的作⽤是:当⽂件读取结束的时候,判断是读取结束的原因是否是:遇到⽂件尾结束。
1. ⽂本⽂件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
例如:
•fgetc 判断是否为 EOF .
•fgets 判断返回值是否为 NULL .
⼆进制⽂件的读取结束判断,判断返回值是否⼩于实际要读的个数。
例如:
•fread判断返回值是否⼩于实际要读的个文件
七.文件缓冲区
缓冲区是在内存区预留的空间,用于暂时存放文件读写期间的数据 。
在计算机系统里,数据的流转离不开内存与硬盘。程序运行时,内存中划分出了不同区域。其中,程序数据区存放着程序运行相关的数据。当数据要从硬盘读取到程序中时,会先进入内存里的输入缓冲区,再提供给程序数据区使用。而当程序要把数据存回硬盘时,数据会先暂存在输出缓冲区,之后再写入硬盘。这就像在硬盘和程序数据区之间,分别设置了两个“中转站”,让数据的传输更高效、有序。
以下是详细介绍:
1.作用
- 提升读写效率:减少对外部存储设备(如硬盘)的直接读写次数。比如读文件时,不是每次读操作都直接从硬盘读取少量数据,而是先将一批数据读入缓冲区,后续从缓冲区获取,降低硬盘I/O操作频率 ;写文件时,数据先存缓冲区,满了或特定条件下再写入硬盘。
- 缓和速度差异:缓和CPU运算速度与I/O设备读写速度不匹配的矛盾。CPU运算快,I/O设备读写慢,有了缓冲区,CPU不用等待I/O设备读写完成,可继续执行其他任务 。
2.分类
- 输入缓冲区:读文件时,硬盘等存储设备的数据先读入此缓冲区,再供程序使用 。
- 输出缓冲区:写文件时,程序数据先存入该缓冲区,满足一定条件(如缓冲区满、执行刷新操作等)时,再写入外部存储设备 。
3.刷新方式
- 行缓冲:遇到换行符 \n 时,将缓冲区数据刷新到目标位置(如显示器、文件等) 。常用于标准输出(显示器)等场景,如 printf 函数默认行缓冲,输出内容有 \n 时才显示 。
- 全缓冲:缓冲区被数据填满后,才将数据刷新到目标位置 。文件操作多采用这种方式,可减少磁盘I/O次数,提高效率 。
- 无缓冲:数据不暂存缓冲区,直接进行读写操作 。较少用,如 stderr 通常无缓冲,确保错误信息及时输出 。
4.相关操作
- 关闭文件: fclose 函数关闭文件时,会自动刷新缓冲区,保证缓冲区数据写入文件 。
- 手动刷新:使用 fflush 函数可手动刷新输出缓冲区,如 fflush(stdout) 刷新标准输出缓冲区 ,让数据立即显示。数