【C语言】文件操作 -- 详解(上)https://developer.aliyun.com/article/1514532?spm=a2c6h.13148508.setting.29.4b904f0ejdbHoA
3、字符输出函数 fputc
- 将参数 char 指定的字符写入到指定的流 stream 中,并把位置标识符向前移动 (字符必须为一个无符号字符)。适用于所有输出流。
#include <stdio.h> int main() { FILE* pf = fopen("test.txt", "w"); if (pf == NULL) { perror("fopen"); return 1; } fputc('a', pf); fputc('b', pf); fputc('c', pf); fclose(pf); pf = NULL; return 0; }
abc 成功被写入。
倘若将代码修改为:
#include <stdio.h> int main() { FILE* pf = fopen("test.txt", "w"); if (pf == NULL) { perror("fopen"); return 1; } /*fputc('a', pf); fputc('b', pf); fputc('c', pf);*/ fclose(pf); pf = NULL; return 0; }
此时再次运行,我们发现那个文件里的内容不见了(大小也变为0kb):
4、文本行输入函数 fgets
- 从指定的流 stream 读取一行,并把它存储在 string 所指向的字符串中,当读取(n-1)个字符时,或者读取到换行符、到达文件末尾时,它会停止,具体视情况而定。适用于所有输入流。
注意:假如 n 是 100,读取到的就是 99 个字符(n-1),因为要留一个字符给 '\0'。
#include <stdio.h> int main() { char arr[10] = "xxxxxx"; // 存放处 FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } fgets(arr, 4, pf); printf("%s\n", arr); fgets(arr, 4, pf); printf("%s\n", arr); fclose(pf); pf = NULL; return 0; }
5、文本行输出函数 fputs
- 将字符串写入到指定的流 stream 中(不包括空字符)。适用于所有输出流。
#include <stdio.h> int main() { FILE* pf = fopen("test.txt", "w"); if (pf == NULL) { perror("fopen"); return 1; } fputs("abcdef", pf); fputs("123456", pf); fclose(pf); pf = NULL; return 0; }
如果想要在 "abcdef" 后面换行,只需在 fputs("abcdef\n", pf); 加上换行符即可。
6、 格式化输入函数 fscanf
- fscanf 用于对格式化的数据进行读取,从流 stream 读取格式化输入。适用于所有输入流。
#include <stdio.h> struct Student { char name[10]; int id; float score; }; int main() { struct Student s1 = { 0 }; // 对格式化的数据进行写文件 FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } fscanf(pf, "%s %d %f", s1.name, &(s1.id), &(s1.score)); printf("%s %d %f\n", s1.name, s1.id, s1.score); fclose(pf); pf = NULL; return 0; }
注意: p1.name本身就是地址(不用&)。
7、格式化输出函数 fprintf
#include<stdio.h> struct S { char arr[10]; int num; float sc; }; int main() { struct S s = { "abcdef", 10, 5.5f }; FILE* pf1 = fopen("test.txt", "w"); if(pf1 == NULL) { perror("fopen"); return 1; } fprintf(pf1, "%s %d %f", s.arr, s.num, s.sc); // 将结构体格式化的数据写入文件 fclose(pf1); pf1 = NULL; struct S temp = { 0 }; // 用于存储读出的数据 FILE* pf2 = fopen("test.txt", "r"); if(pf2 == NULL) { perror("fopen"); return 1; } fscanf(pf2, "%s %d %f", temp.arr, &(temp.num), &(temp.sc)); // 再把数据从文件中读到结构体中 printf("%s %d %f\n", temp.arr, temp.num, temp.sc); // 打印 fclose(pf2); pf2 = NULL; return 0; }
8、二进制输入函数 fread
- 从流中读取,从给定流 stream 读取数据到 buffer 所指向的数组中。
#include <stdio.h> // 二进制的形式读 struct S { char arr[10]; int num; float score; }; int main() { struct S s = { 0 }; FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } fread(&s, sizeof(struct S), 1, pf); printf("%s %d %f", s.arr, s.num, s.score); fclose(pf); pf = NULL; return 0; }
9、二进制输出函数 fwrite
- 写一个数据到流中去,把 buffer 所指向的数组中的数据写入到给定流 stream 中。
#include <stdio.h> // 二进制的形式写 struct S { char arr[10]; int num; float score; }; int main() { struct S s = { "abcde", 10, 5.5f }; FILE* pf = fopen("test.txt", "w"); if (pf == NULL) { perror("fopen"); return 1; } fwrite(&s, sizeof(struct S), 1, pf); fclose(pf); pf = NULL; return 0; }
为什么是乱码?为什么 abcde 不是乱码?
- 我们刚才用的都是文本编译器,文本编译器打开二进制形式的文件完全是两种状态。
- 因为字符串以文本形式写进去和以二进制形式写进去是一样的,但是对于整数、浮点数等来说就不一样了,文本形式写入和二进制形式写入完全是两个概念。
结论:fwrite 和 fread 是一对,fwrire 写进去用 fread 读。
八、文件的随机读写
1、文件指针定位函数 fseek
- 根据文件指针的位置和偏移量来定位文件指针。
int fseek (FILE* stream, long int offset, int origin); // offset是偏移量,origin是起始位置,有三种选项: // 1、SEEK_CUR - 当前文件指针的位置开始偏移。 // 2、SEEK_END - 文件的末尾位置开始偏移。 // 3、SEEK_SET - 文件的起始位置开始偏移。
#include <stdio.h> int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } int ch = fgetc(pf); printf("%c\n", ch); ch = fgetc(pf); printf("%c\n", ch); ch = fgetc(pf); printf("%c\n", ch); fclose(pf); pf = NULL; return 0; }
如果我想得到 a a b,该怎么处理呢?
#include <stdio.h> int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } int ch = fgetc(pf); printf("%c\n", ch); // 调整文件指针 fseek(pf, -1, SEEK_CUR); // SEEK_CUR为当前文件指针位置,偏移量为-1,向前移动1个单位 ch = fgetc(pf); printf("%c\n", ch); ch = fgetc(pf); printf("%c\n", ch); fclose(pf); pf = NULL; return 0; }
⚪用 SEEK_SET ,打印 a d e:
#include <stdio.h> int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } int ch = fgetc(pf); printf("%c\n", ch); // 调整文件指针 fseek(pf, 2, SEEK_CUR); // SEEK_SET为文件的起始位置,偏移量为2,向后移动2个单位 ch = fgetc(pf); printf("%c\n", ch); ch = fgetc(pf); printf("%c\n", ch); fclose(pf); pf = NULL; return 0; }
⚪用 SEEK_END ,打印 a e f :
#include <stdio.h> int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } int ch = fgetc(pf); printf("%c\n", ch); // 调整文件指针 fseek(pf, -2, SEEK_END); // SEEK_END为当前文件末尾位置,偏移量为-2,向前移动2个单位 ch = fgetc(pf); printf("%c\n", ch); ch = fgetc(pf); printf("%c\n", ch); fclose(pf); pf = NULL; return 0; }
2、返回偏移量函数 ftell
- 返回文件指针相对于起始位置的偏移。
long int ftell ( FILE * stream );
#include <stdio.h> int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } // 调整文件指针 fseek(pf, 5, SEEK_CUR); // SEEK_CUR为当前文件指针位置,偏移量为5,向后移动5个单位 int ch = fgetc(pf); printf("%c\n", ch); // f // 返回偏移量 int ret = ftell(pf); printf("%d\n", ret); // 6 fclose(pf); pf = NULL; return 0; }
3、文件指针回到起始位置函数 rewind
- rewind(意为倒带,磁带倒带),设置文件位置为给定流 stream 的文件的开头,让文件指针回到起始位置。
#include <stdio.h> int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } // 调整文件指针 fseek(pf, 5, SEEK_CUR); // SEEK_CUR为当前文件指针位置,偏移量为5,向后移动5个单位 // 返回偏移量 int loc = ftell(pf); printf("fseek调整文件指针后:%d\n", loc); // 6 // 让文件指针回到起始位置 rewind(pf); // 再次返回偏移量,看看是不是回到起始位置了 loc = ftell(pf); printf("使用rewind后:%d\n", loc); // 6 fclose(pf); pf = NULL; return 0; }
九、文件读取结束的判定
1、被错误使用的 feof
- 在文件结束时,判断文件因为何种原因导致文件结束的函数,判断是因为读取失败而结束,还是因为遇到文件尾而结束。如果文件结束,则返回非 0 值,否则返回 0。
注意:feof 函数是个经常被错误使用的一个函数。在文件读取过程中,不能用 feof 函数的返回值直接判断文件是否结束!feof 函数绝对不是用来判断文件是否结束的函数!feof 不是用来判定文件是否结束了的,还是在文件已经结束时,判断是什么原因导致文件结束的。
⚪文本文件读取是否结束,判断返回值是否为EOF (fgetc),或者NULL(fgets)
- fgetc 判断是否为 EOF。
- fgets 判断返回值是否为 NULL。
#include <stdio.h> #include <stdlib.h> int main() { int ch = 0; // 注意:int,非char,要求处理EOF FILE* pf = fopen("test.txt", "r"); if (!pf) { perror("fopen"); return EXIT_FAILURE; // 符号常量EXIT_FAILURE,表示没有成功地执行一个程序 } // fgetc - 当读取失败的时候或者遇到文件结束的时候,都会返回EOF while ( (ch = fgetc(pf)) != EOF ) { putchar(ch); } printf("\n"); // 判断文件结束的原因 if (ferror(pf)) // ferror - 检查是否出现错误 { puts("读取失败错误(I/O error when reading)"); } else if (feof(pf)) { puts("遇到文件尾而结束(End of file reached successfully)"); } fclose(pf); pf = NULL; }
⚪二进制文件的读取结束判断,判断返回值是否小于实际要读的个数
- fread 判断返回值是否小于实际要读的个数。
#include <stdio.h> enum { SIZE = 5 }; int main(void) { double a[SIZE] = {1.0,2.0,3.0,4.0,5.0}; double b = 0.0; size_t ret_code = 0; FILE *fp = fopen("test.bin", "wb"); // 必须用二进制模式 fwrite(a, sizeof(*a), SIZE, fp); // 写double的数组 fclose(fp); fp = fopen("test.bin","rb"); // 读 double 的数组 while((ret_code = fread(&b, sizeof(double), 1, fp))>=1) { printf("%lf\n",b); } if (feof(fp)) { printf("Error reading test.bin: unexpected end of file\n"); } else if (ferror(fp)) { perror("Error reading test.bin"); } fclose(fp); fp = NULL; }
2、正确判定文件是否读取结束的方法
- 文本文件读取是否结束,判断返回值是否为 EOF(fgetc),或者 NULL(fgets)。
- fgetc 函数在读取结束时会返回 EOF,正常读取时,返回读取到的字符的 ASCII 码值。
- fgets 函数在读取结束时会返回 NULL,正常读取时,返回存放字符串的空间的起始地址。
- fread 函数在读取结束时会返回实际读取到的完整元素的个数,如果发现读取到的完整的元素个数小于指定的元素个数,那么就是最后一次读取了。