目录
一.概念梳理
1.文件的分类
2.程序运行时,内存和外存数据交流的过程
二.文件缓冲区
三.常用的文件读写函数笔记
1.常用格式化文件读写函数
(1)格式化文件写入函数
用fprintf进行文件写入操作:
(2)格式化文件读取函数
用fscanf进行文件读取
2.常用的二进制文件读写函数
(1)二进制文件写入函数
fwrite函数写入文件示例:
(2)二进制文件读取函数
四.文件的随机读写
五. 文件读取结束后的相关判定
文件读取的结束标志:
应用实例:
六 .附
一.概念梳理
1.文件的分类
程序文件运行时会向操作系统申请使用内存空间(内存的读写效率十分高)
除操作系统的内存空间以外,其余的数据存储介质可以称为外存(比如硬盘,网络等)
数据文件还可以进一步分为两类:
根据数据的组织形式,数据文件可分为文本文件或者二进制文件。
文本文件:在外存上以ASCII码的形式存储,在存储前需要将内存中的二进制数据进行转换,以ASCII字符的形式存储的文件就是文本文件(文本文件中存储的都是字符类型数据)二进制文件:数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件(二进制文件中存储的都是二进制数据)。
举个例子:
如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节(VS2013测试)
代码实测:
2.程序运行时,内存和外存数据交流的过程
计算机各存储区块发生的数据传递都要经过复杂的中间过程,这些中间过程被抽象为流的概念并且被开发人员统一封装起来以方便编码人员使用。流由文件信息区进行管理,用户通过FILE*的文件指针来维护文件信息区。
一个C程序在运行时,会默认打开三个流:
stdin: 标准输入流 (可通过FILE*类型指针来维护) (与scanf相关的流)
stdout: 标准输出流 (可通过FILE*类型指针来维护) (与printf相关的流)
stderr: 标准错误流 (可通过FILE*类型指针来维护)
二.文件缓冲区
数据从C程序内存流出或从外存流入C程序内存过程中间会被暂时先存入一个文件缓冲区。
系统会自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。
1.从程序内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。
2.从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等).
如果程序运行过程中,我们不想让数据停留在数据缓冲区,可以用fflush函数刷新缓冲区,就可以立马完成数据的传递。
缓冲区的大小根据C编译系统决定的。
编码验证程序缓冲区的存在:
fflush函数:int fflush( FILE *stream );
int main() { FILE* pf = fopen("test.txt", "w"); fputs("abcdef", pf); //先将字符串放在输出缓冲区 printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n"); Sleep(10000); printf("刷新缓冲区\n"); fflush(pf); //刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘) printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n"); Sleep(10000); fclose(pf); //注:fclose在关闭文件的时候,也会刷新缓冲区 pf = NULL; return 0; }
因为有缓冲区的存在,C语言在操作文件的时候,需要及时刷新缓冲区或者在文件操作结束的时候关闭文件。
否则,可能导致读写文件的问题(类似于游戏的坏档)
三.常用的文件读写函数笔记
1.常用格式化文件读写函数
(1)格式化文件写入函数
fprintf函数首部: int fprintf( FILE *stream, const char *format , ......);
函数的功能是将格式化的数据写入到FILE*stream维护的文件中,使用的方法类似于printf函数(printf函数是将格式化的数据写入到屏幕终端上打印出来),由于fprintf适用于所有输出流,所以将stdout(输出流)作为fprint的FILE*类型的实参则fprint可以用于替代print(print只能用于将数据输出到屏幕终端上).
函数返回写入的数据的字节数
fprintf替代printf示例:
从上图例子中可以看到两者的作用完全相同。
fprintf的形参表中的...表示的是可变参数,是一种非常特殊的形参(形参的类型和个数都是可变的)。
用fprintf进行文件写入操作:
struct S { char name[20]; int age; float score; }; int main() { struct S s = { "zhangsan", 20, 95.5 }; FILE* pf = fopen("test.txt", "w"); if (NULL == pf) { perror("fopen"); return 1; } //格式化的写入文件 fprintf(pf, "%s %d %f\n", s.name, s.age, s.score); fprintf(pf, "%s %d %f\n", s.name, s.age, s.score); fprintf(pf, "%s %d %f\n", s.name, s.age, s.score); fprintf(pf, "%s %d %f\n", s.name, s.age, s.score); //关闭文件 fclose(pf); pf = NULL; return 0; }
(2)格式化文件读取函数
fsacnf函数首部:int fscanf( FILE *stream, const char *format ,... );
函数的功能是将FILE*stream维护的文件中的内容以格式化的方式读取到指定的程序内存空间中,使用的方式类似于scanf(scanf只能用于从终端中读取数据),同样地,如果将stdin作为fscanf的FILE*实参,fsanf也可以替代scanf。
函数返回所读取到的格式化数据个数
fscanf替代scanf示例:
struct S { char name[20]; int age; float score; }; int main() { struct S s = { 0 }; fscanf(stdin, "%s %d %f", s.name, &(s.age), &(s.score)); fprintf(stdout, "%s %d %f\n", s.name, s.age, s.score); return 0; }
用fscanf进行文件读取
struct S { char name[20]; int age; float score; }; int main() { struct S s = { 0 }; FILE* pf = fopen("test.txt", "r"); if (NULL == pf) { perror("fopen"); return 1; } //格式化的读取文件 fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score)); //打印看数据 printf("%s %d %f\n", s.name, s.age, s.score); //关闭文件 fclose(pf); pf = NULL; return 0; }
2.常用的二进制文件读写函数
由于数据在内存中是以二进制的形式存储的,因此二进制文件读写操作的过程中无须对数据进行转化(我个人认为以二进制的形式读写文件效率应该会更高)
(1)二进制文件写入函数
fwrite函数首部:
size_t fwrite(const void *buffer, size_t size, size_t count, FILE *stream );
函数的功能是将count个size字节大小的对象以二进制序列的形式从buffer指向的空间写入到FILE*所维护的文件中。写入到文件中的数据将保留内存中原有的二进制序列的形式。
函数返回写入的格式化数据个数,返回值若小于count则说明发生了写入异常。
fwrite函数写入文件示例:
struct S { char name[20]; int age; float score; }; int main() { struct S s = { "张三", 20, 98.5}; FILE* pf = fopen("test.txt", "wb"); if (NULL == pf) { perror("fopen"); return 1; } //写文件 fwrite(&s, sizeof(struct S), 1, pf); //关闭文件 fclose(pf); pf = NULL; return 0; }
(2)二进制文件读取函数
fread函数首部:size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
函数的功能将FILE*stream维护的文件中count个size字节大小的二进制数据读取到buffer所指向的程序变量空间中。
函数返回值是读取的size字节大小的数据个数
fread函数读取示例:
struct S { char name[20]; int age; float score; }; //测试二进制的写函数:fread int main() { struct S s = { 0}; FILE* pf = fopen("test.txt", "rb"); if (NULL == pf) { perror("fopen"); return 1; } //读文件 int ret = fread(&s, sizeof(struct S), 1, pf); printf("%s %d %f\n", s.name, s.age, s.score); //关闭文件 fclose(pf); pf = NULL; return 0; }
四.文件的随机读写
文件读写时,会有一个文件读写指针指向文件中被读写的空间(指针会随着读写的过程进行偏移),一些函数可以根据文件读写指针的位置和偏移量来定位文件读写指针以及移动文件读写指针。
(1)fseek函数
int fseek ( FILE * stream, long int offset, int origin );
参数origin有三种选择:(三个标准库中的常量)三个常量分别代表文件读写指针的参考起始位置。
函数的功能是将文件读写指针移动到相对于起始位置的offset偏移量(以字节为单位)处。
#include <stdio.h> int main() { FILE* pFile; pFile = fopen("example.txt", "wb"); fputs("This is an apple.", pFile); fflush(pFile); fseek(pFile, 9, SEEK_SET); fputs(" sam", pFile); fclose(pFile); return 0; }
(2)ftell函数
long int ftell ( FILE * stream );
函数的功能是返回文件读写指针相对于起始位置的偏移量#include <stdio.h> int main() { FILE* pFile; long size; pFile = fopen("test.txt", "rb"); if (pFile == NULL) perror("Error opening file"); else { fseek(pFile, 0, SEEK_END); // non-portable size = ftell(pFile); fclose(pFile); printf("Size of myfile.txt: %ld bytes.\n", size); } return 0; }
文件中的二进制数据总大小恰好为28个字节
(3)rewind函数
void rewind ( FILE * stream );函数功能是让文件读写指针回到起始位置
int main() { int n; FILE* pFile; char buffer[27]; pFile = fopen("myfile.txt", "w+"); for (n = 'A'; n <= 'Z'; n++) { fputc(n, pFile); } rewind(pFile); fread(buffer, 1, 26, pFile); fclose(pFile); buffer[26] = '\0'; puts(buffer); return 0; }
五. 文件读取结束后的相关判定
文件读取的结束标志:
1. 判断文本文件读取是否结束:
fgetc 判断返回值是否为 EOF (若为EOF则说明读取结束)
fgets 判断返回值是否为 NULL(若为NULL则说明读取结束)2.二进制文件的读取结束判断,判断返回值是否小于实际要读的个数
fread判断返回值是否小于传入的实参count
文件读取结束后,需要对读取结束的原因进行判定这时就需要用到feof函数和ferror函数
feof函数:int feof( FILE *stream );
ferror函数:int ferror( FILE *stream );
文件读取结束后调用上述两个函数
调用feof若返回值为真,则说明是文件正常读取遇到了结束标志而结束的。
调用ferror若返回值为假,则说明文件是在读取过程中出错了而结束的。
应用实例:
#include <stdio.h> #include <stdlib.h> int main(void) { // 注意:int,非char,要求处理EOF int c; FILE* fp = fopen("test.txt", "r"); if (!fp) { perror("File opening failed"); return EXIT_FAILURE; } //fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF while ((c = fgetc(fp)) != EOF) { putchar(c); } //判断是什么原因结束的 if (ferror(fp)) puts("I/O error when reading"); else if (feof(fp)) puts("End of file reached successfully"); fclose(fp); }
六 .附
sscanf函数首部:int sscanf( const char *buffer, const char *format ,... );
函数的功能是将buffer指向的字符串中的数据进行以format规定的格式转换,读取到可变参数实参指针指向的空间中
sprintf函数首部:int sprintf( char *buffer, const char *format , ... );
函数的功能是将可变参数实参指针指向的空间中的数据以format规定的格式转换为字符串并写入buffer指向的空间中