目录
今天接下来我们讲解文件读写函数。🆗🆗🆗

流是一个高度抽象的概念!!在我们写程序的时候,我们需要将数据传到屏幕,存在硬盘上,传到网络上,U盘光盘等外部设备。不同的外部设备的读写方式不同,传输方式也不同。 有人觉得程序员也太难了,于是抽象化了流的概念。我们先将数据统一传输到流里面(文件流等),然后再传输到各个外部设备。

我们在写文件的时候,需要打开文件,关闭文件。我们用scanf从键盘上读取数据,printf向屏幕上打印数据,直接操作了为什么没有打开标准输入输出流呢?
那是因为C语言程序运行起来,就默认打开了3个流。
用打开标准输出流的方式打印26个字母
- #include
- int main()
- {
- char ch = 0;
- for (ch = 'a'; ch <= 'z'; ch++)
- {
- if (ch % 5 == 0)
- fputc('\n', stdout);//标准输出流
- fputc(ch, stdout);
- }
- //a-97
- //b-98
- //c-99
- //100换行了
- return 0;
- }

用打开文件流的方式打印26个字母。------>下面fputc
- #include
- int main()
- {
- FILE* pf = fopen("data.txt", "w");
- if (pf == NULL)
- {
- perror("fopen");
- return 1;
- }
- char ch = 0;
- for (ch = 'a'; ch <= 'z'; ch++)
- {
- if (ch % 5 == 0)
- fputc('\n', pf);//文件流
- fputc(ch, pf);
- }
- //a-97
- //b-98
- //c-99
- //100换行了
- fclose(pf);
- pf = NULL;
- return 0;
- }
记住:站在内存数据的角度去理解!! 其次文件要用输入输出函数去输入输出操作,也可以动键盘修改文本文件!!
- 功能 函数名 适用于
-
- 字符输入函数 fgetc 所有输入流
- 字符输出函数 fputc 所有输出流
- 文本行输入函数 fgets 所有输入流
- 文本行输出函数 fputs 所有输出流
- 格式化输入函数 fscanf 所有输入流
- 格式化输出函数 fprintf 所有输出流
- 二进制输入 fread 文件
- 二进制输出 fwrite 文件


我们接下来详细的介绍以上各个函数。函数头文件 参数 返回值 使用去介绍
fgetc - C++ Reference (cplusplus.com)

- #include
- int main()
- {
- FILE* pf = fopen("data.txt", "r");
- if (pf == NULL)
- {
- perror("fopen");
- return 1;
- }
- int ch=fgetc(pf);
- printf("%c", ch);
-
- ch=fgetc(pf);
- printf("%c", ch);
-
- ch=fgetc(pf);
- printf("%c", ch);
-
- ch=fgetc(pf);
- printf("%c", ch);
-
- ch=fclose(pf);
- pf = NULL;
- return 0;
- }

fputc - C++ Reference (cplusplus.com)
- #include
- int main()
- {
- FILE* pf = fopen("data.txt", "w");
- if (pf == NULL)
- {
- perror("fopen");
- return 1;
- }
- fputc('a', pf);//文件流
- fputc('b', pf);
- fputc('c', pf);
- fputc('d', pf);
-
- fclose(pf);
- pf = NULL;
- return 0;
- }

请在文件中输入26个英文字母。
- #include
- int main()
- {
- FILE* pf = fopen("data.txt", "w");
- if (pf == NULL)
- {
- perror("fopen");
- return 1;
- }
- char ch = 0;
- for (ch='a'; ch <= 'z'; ch++)
- fputc(ch, pf);
- fclose(pf);
- pf = NULL;
- return 0;
- }
- //换行
- #include
- int main()
- {
- FILE* pf = fopen("data.txt", "w");
- if (pf == NULL)
- {
- perror("fopen");
- return 1;
- }
- char ch = 0;
- for (ch = 'a'; ch <= 'z'; ch++)
- {
- if (ch % 5 == 0)
- fputc('\n', pf);//文件流
- fputc(ch, pf);
- }
- //a-97
- //b-98
- //c-99
- //100换行了
- fclose(pf);
- pf = NULL;
- return 0;
- }

fgets - C++ Reference (cplusplus.com)

从流中获取字符串从流中读取字符并将其作为 C 字符串存储到 str 中,直到读取 (num-1) 个字符或到达换行符或文件末尾,以先发生者为准。
换行符使 fgets 停止读取,但它被函数视为有效字符,并包含在复制到 str 的字符串中。
终止空字符会自动附加到复制到 str 的字符之后
只会读num-1个字符
- #include
- int main()
- {
- FILE* pf = fopen("data.txt", "r");
- if (pf == NULL)
- {
- perror("fopen");
- return 1;
- }
-
- char str[10] = { 0 };
- int ret=fgets(str,7,pf);//实际上只会读6个字符
- if (ret == EOF)
- {
- perror("fgets");
- }
- else
- printf("%s", str);
- fclose(pf);
- pf = NULL;
- return 0;
- }

读到\n停止不读了
- #include
- int main()
- {
- FILE* pf = fopen("data.txt", "r");
- if (pf == NULL)
- {
- perror("fopen");
- return 1;
- }
-
- char str[100] = { 0 };
- int ret=fgets(str,12,pf);//实际上只会读6个字符
- if (ret == EOF)
- {
- perror("fgets");
- }
- else
- printf("%s", str);
- fclose(pf);
- pf = NULL;
- return 0;
- }

fputs - C++ Reference (cplusplus.com) 
- #include
- int main()
- {
- FILE* pf = fopen("data.txt", "w");
- if (pf == NULL)
- {
- perror("fopen");
- return 1;
- }
- char str[] = "abcdef";
- fputs(str,pf);
- fputs("abcdef", pf);
- //两种写法
- fclose(pf);
- pf = NULL;
- return 0;
- }

下面两个函数用结构体去举例子 fscanf是输入 fpirntf是输出,先看下对比。
你发现了什么??
fscanf - C++ Reference (cplusplus.com)

- #include
- struct S
- {
- char c;
- int i;
- float a;
- };
- int main()
- {
- FILE* pf = fopen("data.txt", "r");
- if (pf == NULL)
- {
- perror("fopen");
- return 1;
- }
- //输入
- //struct S s = { 't',7,3.14 };
- //fprintf(pf,"%c %d %f",s.c,s.i,s.a);
- //格式必须一摸一样
- //输出
- struct S s = {0};
- fscanf(pf, "%c %d %f", &(s.c), &(s.i), &(s.a));
- printf("%c %d %f", s.c, s.i, s.a);
-
- fclose(pf);
- pf = NULL;
- return 0;
- }

fprintf - C++ Reference (cplusplus.com)

- #include
- struct S
- {
- char c;
- int i;
- float a;
- };
- int main()
- {
- FILE* pf = fopen("data.txt", "w");
- if (pf == NULL)
- {
- perror("fopen");
- return 1;
- }
-
- struct S s = { 't',7,3.14 };
- fprintf(pf,"%c %d %f",s.c,s.i,s.a);
-
- fclose(pf);
- pf = NULL;
- return 0;
- }

前面的函数都是针对于文本文件的,下面这组函数针对的是二进制文件。
fread - C++ Reference (cplusplus.com)

从流中读取数据块从流中读取计数元素数组,每个元素的大小为字节,并将它们存储在 ptr 指定的内存块中。
流的位置指示器按读取的总字节数前进。
如果成功,则读取的总字节数为(大小*计数)。
- #include
- int main()
- {
- FILE* pf = fopen("data.txt", "r");
- if (pf == NULL)
- {
- perror("fopen");
- return 1;
- }
- int arr[20] = { 0};//10个整型40个字节
- fread(arr, 4, 10, pf);
- int i = 0;
- for (i = 0; i < 10;i++)
- {
- printf("%d ", arr[i]);
- }
- fclose(pf);
- pf = NULL;
- return 0;
- }

fwrite - C++ Reference (cplusplus.com)

写入要流式传输的数据块将计数元素数组(每个元素的大小为字节)从 ptr 指向的内存块写入流中的当前位置。
流的位置指示器按写入的总字节数前进。
在内部,该函数解释所指向的块,就好像它是一个类型的元素数组,并按顺序写入它们,就好像为每个字节调用一样。
- #include
- int main()
- {
- FILE* pf = fopen("data.txt", "w");
- if (pf == NULL)
- {
- perror("fopen");
- return 1;
- }
- int arr[] = { 1,2,3,4,5,6,7,8,9,10 };//10个整型40个字节
- fwrite(arr, 4, 10, pf);
-
- fclose(pf);
- pf = NULL;
- return 0;
- }

前面我们已经学习过了scanf&printf 和 fscanf&fprintf 我们再来学习一下sscanf&sprintf
sscanf - C++ Reference (cplusplus.com) 
- //需要结构体
- //sprintf和sscanf配合使用
- //两个才能达到效果
- #include
- struct S
- {
- int a;
- float b ;
- char c;
- };
- int main()
- {
- struct S s = { 7,3.14,'t' };
- char str[100] = { 0 };
- sprintf(str, "%d_%f_%c", s.a, s.b, s.c);
- printf("%s\n", str);
-
-
- struct S tmp = { 0 };
- sscanf(str, "%d_%f_%c", &(tmp.a), &(tmp.b), &(tmp.c));
- printf("%d\n", tmp.a);
- printf("%f\n", tmp.b);
- printf("%c\n", tmp.c);
- return 0;
- }

sprintf - C++ Reference (cplusplus.com)

- #include
- int main()
- {
- int a = 7;
- float b = 3.14;
- char c = 't';
- char str[100] = { 0 };
- sprintf(str, "%d_%f_%c", a, b, c);
- printf("%s", str);
- return 0;
- }

最后我们来对比一下底下几组函数

fseek - C++ Reference (cplusplus.com)


- #include
- int main()
- {
- FILE* pf = fopen("data.txt", "r");
- if (pf == NULL)
- {
- perror("fopen");
- return 1;
- }
- int ch=fgetc(pf);
- printf("%c", ch);
-
- ch=fgetc(pf);
- printf("%c", ch);
-
- ch=fgetc(pf);
- printf("%c", ch);
-
- ch=fgetc(pf);
- printf("%c", ch);
-
- fseek(pf, -4, SEEK_CUR);//移动光标的作用
- fseek(pf, 0, SEEK_SET);
- fseek(pf, -4, SEEK_END);
- ch = fgetc(pf);
- printf("%c", ch);
-
- close(pf);
- pf = NULL;
- return 0;
- }

特别提醒:如果我们想熟练掌握这个函数运用,我们必须对我们要读取的文件格式内容了如指掌
ftell - C++ Reference (cplusplus.com)

- #include
- int main()
- {
- FILE* pf = fopen("data.txt", "r");
- if (pf == NULL)
- {
- perror("fopen");
- return 1;
- }
- int ch=fgetc(pf);
- printf("%c", ch);
-
- ch=fgetc(pf);
- printf("%c", ch);
-
- ch=fgetc(pf);
- printf("%c", ch);
-
- ch=fgetc(pf);
- printf("%c", ch);
-
- long int ret=ftell(pf);
- printf("%d", ret);
-
- close(pf);
- pf = NULL;
- return 0;
- }

rewind - C++ Reference (cplusplus.com)

- #include
- int main()
- {
- FILE* pf = fopen("data.txt", "r");
- if (pf == NULL)
- {
- perror("fopen");
- return 1;
- }
- int ch=fgetc(pf);
- printf("%c", ch);
-
- ch=fgetc(pf);
- printf("%c", ch);
-
- ch=fgetc(pf);
- printf("%c", ch);
-
- ch=fgetc(pf);
- printf("%c", ch);
-
- rewind(pf);
-
- ch = fgetc(pf);
- printf("%c", ch);
-
- close(pf);
- pf = NULL;
- return 0;
- }

特别提醒:以上函数移动的都是文件状态指针,和指向文件信息区的指针没有关系(它没动!)
被错误使用的feof
牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。
而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
feof - C++ Reference (cplusplus.com)
A non-zero value is returned in the case that the end-of-file indicator associated with the stream is set.Otherwise, zero is returned.
ferror - C++ Reference (cplusplus.com)
A non-zero value is returned in the case that the error indicator associated with the stream is set.Otherwise, zero is returned.
练习1:将data1.txt的内容拷贝到data2.txt上面去。
- #include
- int main()
- {
- FILE* pfread = fopen("data1.txt", "r");
- if (pfread == NULL)
- {
- perror("fopen");
- return 1;
- }
-
- FILE* pfwrite = fopen("data2.txt", "w");
- if (pfwrite == NULL)
- {
- perror("fopen");
- fclose(pfread);
- pfread = NULL;
- return 1;
- }
- int ch = 0;
- while( (ch = fgetc(pfread)) != EOF)
- {
- fputc(ch, pfwrite);
- }
-
- if (ferror(pfread))
- puts("I/O error when reading");
- else if (feof(pfread))
- puts("End of file reached successfully");
-
- if (ferror(pfwrite))
- puts("I/O error when reading");
- else if (feof(pfwrite))
- puts("End of file reached successfully");
-
- fclose(pfread);
- pfread = NULL;
- fclose(pfwrite);
- pfwrite = NULL;
- return 0;
- }
练习2:二进制文件
- #include
- 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;
- }
✔✔✔✔✔最后,感谢大家的阅读,若有错误和不足,欢迎指正!
希望大家继续坚持在每天敲代码的路上。其实,没有人会一直带着你往前走,你自己一定要成为自己的救赎。
代码---------→【唐棣棣 (TSQXG) - Gitee.com】
联系---------→【邮箱:2784139418@qq.com】