🌲随机读写
随机读写函数,需要配合上面的输入输出函数使用,所谓的随机读写,是指通过改变文件指针的偏移量,来写入或读取数据。介绍三个和随机读取有关的函数:fseek 改变文件指针偏移量、ftell 查看当前文件指针的偏移量、rewind 使文件指针复原至起始位置。
🌱fseek
//fseek,文件指针偏移量 int main() { FILE* fp = fopen("test.txt", "w"); if (NULL == fp) { perror("fopen::test.txt"); return 1; } char* pc = "abc"; fseek(fp, 20, SEEK_SET);//从起点往后偏移 fputs(pc, fp); fclose(fp); fp = NULL; return 0; }
🌱ftell
//ftell,返回当前文件指针偏移信息 int main() { FILE* fp = fopen("test.txt", "r"); if (NULL == fp) { perror(fp); return 1; } printf("当前文件指针偏移量为:%d\n", ftell(fp)); fseek(fp, 20, SEEK_SET);//向后偏移20 printf("经过fseek设置后的文件指针偏移量为:%d\n", ftell(fp)); fclose(fp); fp = NULL; return 0; }
🌱rewind
//rewind,使文件指针恢复至原位置 int main() { FILE* fp = fopen("test.txt", "r"); if (NULL == fp) { perror("fopen::test.txt"); return 1; } fseek(fp, 20, SEEK_SET);//先让文件指针向后偏移20 printf("当前文件指针偏移量为:%d\n", ftell(fp)); rewind(fp);//使文件指针恢复至起始位置 printf("经过恢复后的文件指针偏移量为:%d\n", ftell(fp)); return 0; }
🌱fseek、ftell、rewind 三合一
//fseek、ftell、rewind三合一 //假设文件中存储数据为abcdef int main() { FILE* fp = fopen("test.txt", "r"); if (NULL == fp) { perror("fopen::test.txt"); return 1; } printf("现在文件中内容为abcdef,我们要依次取出e、b、d\n"); fseek(fp, -2, SEEK_END);//从后往前偏移 printf("先取出字符%c\n", fgetc(fp)); rewind(fp);//还原至起始位置 fseek(fp, 1, SEEK_SET);//从前往后偏移 printf("再取出字符%c\n", fgetc(fp)); fseek(fp, 1, SEEK_CUR);//从当前位置向后偏移 printf("最后再取出字符%c\n", fgetc(fp)); fclose(fp); fp = NULL; return 0; }
注意:
每进行一次文件输入输出操作,文件指针都会向后移动一位。比如上面的三合一, 当我们读取到字符 'b' 后,文件指针向后移动一位,指向字符 'c' ,此时只需要把文件指针向后偏移一位,就能愉快的读取到字符 'd' 了。
🌲文本文件与二进制文件
🌱文本文件
文本文件指以ASCII码(文本方式)存储的数据,原始数据机器能直接看懂,将内存中的数据对应ASCII码解码存储后,我们人类也能看懂,举个栗子,在记事本中写的文本,就是文本文件
🌱二进制文件
二进制文件是将数据编译后转成二进制形式,然后直接存储的文件,这种文件机器能秒懂,读取效率很高(因为不需要转译),但二进制一般人是看不懂的,部分二进制数据也无法通过ASCII码解码为正确的数据,因此强行输出二进制文件,极有可能会得到乱码。比如将上面的那段话通过二进制形式写入文件中,可以看到除字符类型数外,其他类型的数据变成了乱码。
下图为上面的二进制文件在内存中以二进制形式存储的样子,显示为十六进制(节省空间),实际为二进制。
🌱注意
如果待读取的文件中存储的是二进制数据,就需要使用 二进制读取 "rb" 的形式读取数据;反之如果想写入二进制数据,就需要用 二进制写入 "wb" ,无论是二进制还是普通文本,计算机都能读懂,只是我们看不得罢了。小技巧:可以使用二进制存储重要数据,这样外行人一时半会也理解不了。
🌲文件使用注意事项
🌱被错误使用的feof
很多人在写C语言课设的时候(学生信息管理系统、通讯录系统等),会通过 feof 来判断文件是否读取结束,这是一种错误的用法,因为 feof 的作用是判断当前文件读取结束原因的,如果是因为读取到了末尾而结束,feof(fp) 就为真;除了这个以外,还有另一个文件读取结算原因判断函数,ferror ,当 ferror(fp) 为真时,说明此时发生了读取异常,并非正常结束,我们可以通过这两个报错函数来判断文件读取结束的真正原因。
char arr[100] = "0"; fgets(arr, sizeof(arr), fp); printf("%s\n", arr); int n = 0; if ((n = feof(fp))) printf("End by EOF\n"); if (ferror(fp)) printf("End by IO Error\n");
🌱文件读取结束原因判断
既然 feof 不是用来判断读取是否结束的,那说明存在其他判断方法,其实答案就是函数设计中,前辈在设计函数时已经考虑好了,比如 fgetc 没有读取到数据会返回EOF,fgets 没有读取到数据会返回NULL,fscanf 可以通过其返回的实际读取元素个数进行判断,fread 可以通过返回值与指定读取的元素数比较。每种读取函数都有属于的自己的判断方法,比如下面这两个例子:
🪴对文本数据进行读取
//读取错误信息判断 //1.文本文件版,假设文件内已有信息,为abcdef int main() { FILE* fp = fopen("test.txt", "r"); if (NULL == fp) { perror("fopen::test.txt"); return 1; } int ch = 0;//接收读取的数据,要用整型,因为EOF为-1 while ((ch = fgetc(fp)) != EOF) { printf("%c", ch); } //判断是为何结束 if (feof(fp)) printf("\nEnd by EOF(因读到文件末尾而结束)\n"); else if (ferror(fp)) printf("\nEnd by IO(因中途读取失败而结束)\n"); fclose(fp); fp = NULL; return 0; }
🪴对二进制文件进行读取
//2.二进制文件版 enum { SIZE = 5 };//相当于宏定义 int main() { double a[SIZE] = { 1.1,2.2,3.3,4.4,5.5 }; //首先把五个浮点数以二进制的形式,写入文件中 FILE* fp = fopen("testFoBin.txt", "wb"); if (NULL == fp) { perror(fp); return 1; } fwrite(&a, sizeof(*a), SIZE, fp); fclose(fp);//写入完成,关闭文件 double b[SIZE] = { 0.0 }; //现在以二进制的形式读取数据 fp = fopen("testFoBin.txt", "rb"); int size = fread(&b, sizeof(*b), SIZE, fp); if (size == SIZE) { printf("No Error!\n"); int i = 0; while (i < size) printf("%.2lf ", b[i++]); } else { if (feof(fp)) printf("\nError by EOF\n"); else if(ferror(fp)) printf("\nError by IO\n"); } fclose(fp); fp = NULL; return 0; }
🌱文件缓冲区
ANSIC 标准定义了“缓冲文件系统”这个概念,所谓缓冲文件系统是指系统自动地在内存中为程序
中每一个正在使用的文件开辟一块“文件缓冲区”。无论是读取还是写入数据时,都会先将数据送入文件缓冲区,等文件缓冲区装满或遇到刷新指令后,数据才会被读取(写入)到目标空间中。文件缓冲区的大小是由编译器决定的。
🪴验证文件缓冲区是否存在
我们可以利用睡眠函数 Sleep 来使程序暂停,此时数据还没有被写入文件中,仍然位于缓冲区;之后再手动刷新缓冲区,数据此时会被推送至文件中。
//文件缓冲区 #include<windows.h> int main() { //打开文件 FILE* fp = fopen("test.txt", "w"); if (NULL == fp) { perror(fp); return 1; } char* ps = "测试文件缓冲区"; fputs(ps, fp);//先将数据写到缓冲区中 printf("数据现在已经在缓冲区里面了,但还没有推送到文件中\n"); printf("程序睡眠10秒,10秒后刷新缓冲区\n"); Sleep(10000);//睡眠函数,单位是毫秒 fflush(fp); printf("现在缓冲区已经刷新,数据已经写入文件中了\n"); Sleep(10000); //关闭文件,当文件关闭时,缓冲区也会被刷新 fclose(fp); fp = NULL; return 0; }
可以看到文件缓冲区是真实存在的。
注意:
fclose 关闭文件后,会自动刷新缓冲区,数据能够推送至文件
当程序运行结束后,缓冲区也会被自动刷新
scanf 遇到 \n 也会触发缓冲区刷新,另外如果其在读取字符型数据时,遇到空白字符(空格、TAB键)也会触发缓冲区的刷新
🌳总结
以上就是C语言文件操作的所有内容了,从文件的打开到文件的关闭,中间可以进行多种操作,构造出巧妙的数据。当然前提是我们得学会文件的相关操作,可以巧记为单字符读写、行读写、格式化读写和二进制读写,无论是那种操作,都需要和对应的文件操作指令匹配上;关于随机读写,记住那三个偏移量函数就行了;最后需要对文件缓冲区有一定的理解,确保数据能成功推送至文件内。总之,文件操作的学习可以宣布毕业了。
如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正