4.2.2.2.3 使用
int main() { //打开文件 //打开c盘下的test文件夹中的test.txt时,在内存中映射了一块有关此文件的文件信息区, //这个文件信息区的类型是FILE结构体类型,然后把地址给到pf文件指针,就可以通过pf指针来访问这块文件信息区。 FILE* pf = fopen("c:\\test\\test.txt", "r"); if (pf == NULL) { perror("fopen():"); return 1; } //读取数据一行一行读 char arr[20] = "#############"; fgets(arr, 20, pf); printf("%s\n", arr); fgets(arr, 20, pf); printf("%s\n", arr); //关闭文件 //关闭文件就是指通过这个文件指针关闭这个文件信息区,进而关闭此文件 fclose(pf); pf = NULL; return 0; }
运行结果:
为什么会出现这样呢?原因很简单,不管你从流中拿多好,它每次都是从一行里面拿,如果超出了这一行的元素个数,它依旧是从这一行中拿数据,不可能从第二行拿。
另外还要注意num:
通过这个图我们看得出来num是指从pf指向的流中拿到num个字符,其中还包括’\0’结束标志字符。
4.2.3 fscanf和fprintf
4.2.3.1 fprintf库函数
4.2.3.1.1 基本函数参数
int fprintf ( FILE * stream, const char * format, … );
作用:将格式化的数据写入流
stream: 指向标识输出流的FILE对象的指针。
format:C字符串,包含要写入流的文本。它可以有选择地包含嵌入的格式说明符,这些说明符将被后续附加参数中指定的值替换并按要求格式化。
4.2.3.1.2 注意要点
- 如果成功,则返回写入的字符总数。
- 如果发生写错误,则设置错误指示符(ferror)并返回负数。如果在写入宽字符时发生多字节字符编码错误,则errno设置为EILSEQ并返回负数
4.2.3.1.3 使用
#include <stdio.h> struct S { char name[20]; int age; float weight; }; int main() { struct S s = { "lisi",20,123.5f }; FILE* pf = fopen("c:\\test\\test.txt", "w"); if (NULL == pf) { perror("fopen():"); return 1; } fprintf(pf, "%s %d %f", s.name, s.age, s.weight); fclose(pf); pf = NULL; return 0; }
运行结果:
4.2.3.2 fscanf库函数
4.2.3.2.1 基本函数参数
int fscanf ( FILE * stream, const char * format, … );
作用:从流读取格式化的数据
stream: 指向标识输入流的FILE对象的指针。
format:C字符串,包含一个字符序列,该字符序列控制如何处理从流中提取的字符
4.2.3.2.2 注意要点
1.成功时,函数返回成功填充的参数列表的项数
2.如果在读取过程中发生读取错误或到达文件末尾,则设置适当的指示符。并且,如果在成功读取任何数据之前发生任何一种情况,则返回EOF。
4.2.3.2.3 使用
struct S { char name[20]; int age; float weight; }; int main() { struct S s = { "lisi",20,123.5f }; FILE* pf = fopen("c:\\test\\test.txt", "r"); if (NULL == pf) { perror("fopen():"); return 1; } fscanf(pf, "%s %d %f", s.name, &s.age, &s.weight); printf("%s %d %f", s.name, s.age, s.weight); fclose(pf); pf = NULL; return 0; }
运行结果:
4.2.4 fread和fwrite
4.2.4.1 fwrite库函数
4.2.4.1.1 基本函数参数
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
作用:向流写入数据块
ptr: 指向要写入的元素数组的指针,转换为const void*。
size: 要写入的每个元素的大小(以字节为单位)。
count: 元素数量,每个元素的大小为size字节。
stream: 指向指定输出流的FILE对象的指针。
4.2.4.1.2 注意要点
- 返回成功写入的元素总数。
- 如果此数字与count参数不同,则写入错误阻止函数完成。在这种情况下,将为流设置错误指示符,如果size或count为零,则函数返回零,并且错误指示符保持不变。
4.2.4.1.3 使用
#include <stdio.h> struct S { char name[20]; int age; float score; }; int main() { struct S s = { "zhangsan", 20, 95.5f }; FILE*pf = fopen("c:\\test\\test.txt", "wb"); if (NULL == pf) { perror("fopen"); return 1; } fwrite(&s, sizeof(s), 1, pf); fclose(pf); pf = NULL; return 0; }
运行结果:
4.2.4.2 fread库函数
4.2.4.2.1 基本函数参数
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
作用:从流中读取数据块
ptr:指向内存块的指针,其大小至少为(sizecount)个字节,转换为void。
size:要读取的每个元素的大小(以字节为单位)。
count:元素数量,每个元素的大小为size字节。
stream:指向指定输入流的FILE对象的指针。
4.2.4.2.2 注意要点
- 返回成功读取的元素总数。
- 如果该数字与count参数不同,则要么发生了读取错误,要么在读取时已到达文件结束。在这两种情况下,都设置了适当的指标,可以分别用ferror和feof进行检查。如果size或count为零,则函数返回零,并且流状态和ptr所指向的内容保持不变。
4.2.4.2.3 使用
#include <stdio.h> struct S { char name[20]; int age; float score; }; int main() { struct S s = {0}; FILE* pf = fopen("test.txt", "rb"); if (NULL == pf) { perror("fopen"); return 1; } fread(&s, sizeof(s), 1, pf); printf("%s %d %f\n", s.name, s.age, s.score); fclose(pf); pf = NULL; return 0; }
运行结果:
5. 对比一组函数
scanf/fscanf/sscanf
printf/fprintf/sprintf
scanf:按照一定的格式从键盘输入数
printf:按照一定的格式把数据打印到屏幕上
这组函数适用于标准输入/输出的格式化的输入/输出语句
fscanf:按照一定的格式从输入流(文件/stdin)输入数据
fprintf:按照一定的格式从输出流(文件/stdout)输出数据
这组函数适用于所有的输入/输出流的格式化输入/输出语句
sscanf:从字符串中按照一定的格式读取出格式化的数据
sprintf:把格式化的数据按照银锭的格式换成字符串
提一下sscanf和sprintf函数用法:
sprintf:
int sprintf ( char * str, const char * format, … );
作用:写入格式化数据到字符串str中
返回值情况:如果成功,则返回写入的字符总数。此计数不包括自动附加在字符串末尾的额外空字符。如果失败,则返回负数。
sscanf:
int sscanf ( const char * s, const char * format, …);
作用:从字符串s中读取格式化数据
返回值情况:成功时,函数返回参数列表中成功填充的项数。在匹配失败的情况下,该计数可以匹配预期的项目数量,也可以更少(甚至为零)。如果在成功解释任何数据之前出现输入失败,则返回EOF。
#include <stdio.h> struct S { char name[10]; int age; float score; }; int main() { char buf[100] = {0}; struct S tmp = { 0 }; struct S s = { "zhangsan", 20, 95.5f }; //从结构体s中写入数据到buf sprintf(buf, "%s %d %f", s.name, s.age, s.score);//以字符串的形式打印 printf("%s\n", buf); //从buf字符串中读取数据 sscanf(buf, "%s %d %f", tmp.name, &(tmp.age), &(tmp.score)); printf("%s %d %f\n", tmp.name, tmp.age, tmp.score);//以结构体的形式打印 return 0; }
6. 文件随机读写
前面我们介绍的是顺序读写,这里我们来介绍一下随机读写。
6.1 fseek函数
int fseek ( FILE * stream, long int offset, int origin );
作用:将与流关联的位置指示器设置为一个新位置(意思就是根据文件指针的位置和偏移量来定位文件指针
)
参数说明:
stream:指向标识流的FILE对象的指针。
offset:二进制文件:要从原点偏移的字节数。
origin:作为偏移量参考的位置。SEEK_SET:文件的开始;SEEK_CUR:文件指针的当前位置;SEEK_END:文件的末尾。
实例:
这里就列举了一个SEEK_SET的例子,其他的很容易就懂了。
6.2 ftell函数
long int ftell ( FILE * stream );
作用:返回文件指针相对于起始位置的偏移量
这里就不再举例说明了,很容易。
6.3 rewind函数
void rewind ( FILE * stream );
作用:让文件指针的位置回到文件的起始位置。
7. 被错误使用的feof函数
在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束,而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
观察上述我们顺序读取的函数就可以清晰知道文件结束是不同的。这个是要重点注意的,因为我们使用的时候要知道是读取失败结束还是遇到文件结尾结束,不然bug就很难找到。
8. 文件缓冲区
ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的(VS2022缓冲区大小是512字节)。
总结:
因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。如果不做,可能导致读写文件的问题。