今天接下来我们讲解文件读写函数。🆗🆗🆗
文件状态指针
- 文件状态指针也就是文件指示器。(可以理解为光标)
- 文件在不断被操作读取的过程中,文件状态指针是一直在移动的。
文件流
- 流是怎样一个概念呢?我们简单通俗的介绍下。本篇我们把所有输入输出流分为两个标准输入出流和文件流
流是一个高度抽象的概念!!在我们写程序的时候,我们需要将数据传到屏幕,存在硬盘上,传到网络上,U盘光盘等外部设备。不同的外部设备的读写方式不同,传输方式也不同。 有人觉得程序员也太难了,于是抽象化了流的概念。我们先将数据统一传输到流里面(文件流等),然后再传输到各个外部设备。
我们在写文件的时候,需要打开文件,关闭文件。我们用scanf从键盘上读取数据,printf向屏幕上打印数据,直接操作了为什么没有打开标准输入输出流呢?
那是因为C语言程序运行起来,就默认打开了3个流。
- stdin : 标准输入流(键盘)
- stdout : 标准输出流(屏幕)
- stderr : 标准错误流
- 特别提醒:所有的流都是FILE*的指针类型
用打开标准输出流的方式打印26个字母
#include<stdio.h> 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<stdio.h> 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
fgetc - C++ Reference (cplusplus.com)
- fgetc是以读的形式把字符输出文件,内存读取数据。
- 头文件 #include<stdio.h>
- 参数FILE *stream 文件指针类型的指针变量 指向了一个文件信息区
- 返回值是int类型(字符发生了整型提升)
- 成功后,将返回字符读取(提升为 int 值)ASCII码。
- 返回类型为 int 以适应特殊值 EOF,该值表示失败-1
- 如果位置指示器位于文件末尾,则该函数返回 EOF 并设置流的 eof 指示器 (feof)。
- 如果发生其他读取错误,该函数还会返回 EOF,但改为设置其错误指示器 (ferror)。
#include<stdio.h> 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
fputc - C++ Reference (cplusplus.com)
- fputc以写的形式输入字符到文件中。
- 头文件#include<stdio.h>
- 参数int character 是整型 也就是字符整型提升 直接写字符即可
- 参数FILE*stream 是文件指针类型的指针变量 指向了一个文件信息区
- 返回值
#include<stdio.h> 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<stdio.h> 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<stdio.h> 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
fgets - C++ Reference (cplusplus.com)
从流中获取字符串
从流中读取字符并将其作为 C 字符串存储到 str 中,直到读取 (num-1) 个字符或到达换行符或文件末尾,以先发生者为准。
换行符使 fgets 停止读取,但它被函数视为有效字符,并包含在复制到 str 的字符串中。
终止空字符会自动附加到复制到 str 的字符之后
- fgets是从文本文件stream读取输出字符串num 到 str的函数
- 头文件#include<stdio.h>
- 参数str是字符指针,指向一个字符数组的指针
- 参数FILE*stream 是文件指针类型的指针变量 指向了一个文件信息区
- 参数num是要复制到 str 的最大字符数(包括终止空字符)(整型提升)
- 返回值是char*类型
- 成功后,函数返回 str。
- 如果在尝试读取字符时遇到文件末尾,则设置 eof 指示器 (feof)。
- 如果在读取任何字符之前发生这种情况,则返回的指针为空指针(str 的内容保持不变)。
- 如果发生读取错误,则设置错误指示器(ferror),并返回空指针(但str指向的内容可能已更改)。
- 读取停止情况
- 读取到了num-1个字符
- 到达换行符\n
- 到达文件末尾
只会读num-1个字符
#include<stdio.h> 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<stdio.h> 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
fputs - C++ Reference (cplusplus.com)
- fputs是将str里的字符输出到文本文件stream
- 头文件#include<stdio.h>
- 参数FILE*stream 是文件指针类型的指针变量 指向了一个文件信息区
- 参数str 是字符指针 指向存放字符串的数组(由const修饰,防止被修改)
- 返回值是int类型
#include<stdio.h> 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是输出,先看下对比。
你发现了什么??
- scanf------->键盘-------->读取到程序中 fscanf-------->文件-------->读取到程序中
- printf-------->程序中的数据-------->输入到屏幕上 fprintf-------->程序中的数据------>输入到文件
fscanf
fscanf - C++ Reference (cplusplus.com)
- fscanf是从文件读取数据
- 头文件#include<stdio.h>
- 参数FILE*stream 是文件指针类型的指针变量 指向了一个文件信息区
- 其他和scanf是一样的,不用关心
- 返回值是int类型
#include<stdio.h> 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
fprintf - C++ Reference (cplusplus.com)
- fprintf是输入数据到文本文件
- 头文件#include<stdio.h>
- 参数FILE*stream 是文件指针类型的指针变量 指向了一个文件信息区
- 其他和printf是一样的,不用关心
- 返回值是int类型
#include<stdio.h> 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
fread - C++ Reference (cplusplus.com)
从流中读取数据块
从流中读取计数元素数组,每个元素的大小为字节,并将它们存储在 ptr 指定的内存块中。
流的位置指示器按读取的总字节数前进。
如果成功,则读取的总字节数为(大小*计数)。
- fread是向二进制文件读取数据
- 头文件#include<stdio.h>
- 参数FILE*stream 是文件指针类型的指针变量 指向了一个文件信息区
- size_t size是每个元素的大小(以字节为单位)
- size_t count是数组元素的个数
- 参数void *ptr指向大小至少为 (size*count) 字节的内存块的指针(const修饰防止修改,void*类型的指针是因为不知道读取的是哪种类型的指针,void*不能被解引用操作,可以强制转换)
- 返回值是size_t类型
- 返回成功读取的元素总数。
- 如果此数字与 count 参数不同,则表示读取时发生读取错误或到达文件末尾。在这两种情况下,都会设置正确的指标,可以分别用 ferror 和 feof 进行检查。
- 如果大小或计数为零,则该函数返回零,并且流状态和 ptr 指向的内容保持不变。
size_t 是无符号整数类型。
- fread要求读取count个大小位size字节的数据
- 如果真的读取到count个数据,函数就返回count
- 如果没有读取到count个数据,函数就返回的是真实的读取到的完整的数据个数
#include<stdio.h> 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
fwrite - C++ Reference (cplusplus.com)
写入要流式传输的数据块
将计数元素数组(每个元素的大小为字节)从 ptr 指向的内存块写入流中的当前位置。
流的位置指示器按写入的总字节数前进。
在内部,该函数解释所指向的块,就好像它是一个类型的元素数组,并按顺序写入它们,就好像为每个字节调用一样。
- fwrite是向二进制文件写输入数据的
- 头文件#include<stdio.h>
- 参数FILE*stream 是文件指针类型的指针变量 指向了一个文件信息区
- 参数void *ptr是指向写入元素数组的的指针变量(const修饰防止修改,void*类型的指针是因为不知道输入的输入是哪种类型的指针,void*不能被解引用操作,可以强制转换)
- size_t size是每个元素的大小(以字节为单位)
- size_t count是数组元素的个数
- 返回值size_t类型
- 返回成功写入的元素总数。
- 如果此数字与 count 参数不同,则写入错误阻止函数完成。在这种情况下,将为流设置错误指示器(ferror)。
- 如果大小或计数为零,则该函数返回零,错误指示器保持不变。
size_t 是无符号整数类型。
#include<stdio.h> 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
sscanf - C++ Reference (cplusplus.com)
- 从s(字符串数组)拿出格式化的数据(字符串),放到每个数据当中去。
- 头文件#include<stdio.h>
- 参数s是指向一块字符串数组的字符指针
- 后面参数和scanf一样
- 格式要和sprintf一模一样,或者和字符串内字符串格式一摸一样
- 返回值是int类型
- 成功后,该函数返回参数列表中成功填充的项数。此计数可以与预期的项目数匹配,或者在匹配失败的情况下更少(甚至为零)。
- 如果在成功解释任何数据之前输入失败,则返回 EOF。
//需要结构体 //sprintf和sscanf配合使用 //两个才能达到效果 #include<stdio.h> 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
sprintf - C++ Reference (cplusplus.com)
- sprintf是把格式化的数据,转化成字符串(写格式化的数据到字符串str里)
- 头文件#include<stdio.h>
- 参数str 是指向一块字符数组的字符指针
- 后面参数和printf一样
- 返回值是int类型
- 成功后,将返回写入的字符总数。此计数不包括自动追加在字符串末尾的其他 null 字符。
- 失败时,返回负数
#include<stdio.h> 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; }
最后我们来对比一下底下几组函数
- scanf 是格式化的输入函数,针对是标准输入流(键盘)
- printf 是格式化的输出函数,针对的是标准输出流(屏幕)
- scanf和printf是针对标准输入和输出流的格式化输入和输出函数
- fscanf 是针对所有输入流(文件流、标准输入流)的格式化输入函数
- fprintf 是针对所有输出流(文件流、标准输出流)的格式化输出函数
- sscanf 将字符串转化成格式化的数据
- sprintf 将格式化的数据转化成字符串
文件的随机读写
fseek
fseek - C++ Reference (cplusplus.com)
- fseek是根据文件指针的位置和偏移量来定位文件指针
- 头文件#include<stdio.h>
- 参数FILE*stream 是文件指针类型的指针变量 指向了一个文件信息区
- 参数offset 表示你选择的位置(当前/开头/结尾)到你需要的位置 偏移量
- 参数origin 表示你选择位置
- 返回值是int类型
- 如果成功,该函数将返回零。
- 否则,它将返回非零值。如果发生读取或写入错误,则设置错误指示器(ferror)
#include<stdio.h> 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
ftell - C++ Reference (cplusplus.com)
- 返回文件指针相对于起始位置的偏移量
- 头文件#include<stdio.h>
- 参数FILE*stream 是文件指针类型的指针变量 指向了一个文件信息区
#include<stdio.h> 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
rewind - C++ Reference (cplusplus.com)
- rewind让文件指针的位置回到文件的起始位置
- 头文件#include<stdio.h>
- 参数FILE*stream 是文件指针类型的指针变量 指向了一个文件信息区
- 无返回值
#include<stdio.h> 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; }
特别提醒:以上函数移动的都是文件状态指针,和指向文件信息区的指针没有关系(它没动!)
文件结束的判定
- 文本文件读取是否结束,判断返回值是否为EOF (fgetc),或者NULL(fgets)
- fgetc判断是否为EOF.
- fgets判断返回值是否为NULL.
- 二进制文件的读取结束判断,判断返回值是否小于<实际要读的个数。
- fread判断返回值是否小于实际要读的个数。
feof
被错误使用的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.
- feof的作用是:当文件读取结束的时候,判断是读取结束的原因是否是:遇到文件结尾结束。
- feof在文件读取结束后,用来判断文件是否因为读取过程中遇到了文件结束标志而结束!
ferror
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.
- ferroe在文件读取结束后,用来判断文件是否因为读取过程中遇到错误而结束!
练习1:将data1.txt的内容拷贝到data2.txt上面去。
#include<stdio.h> 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<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; }
✔✔✔✔✔最后,感谢大家的阅读,若有错误和不足,欢迎指正!
希望大家继续坚持在每天敲代码的路上。其实,没有人会一直带着你往前走,你自己一定要成为自己的救赎。
代码---------→【唐棣棣 (TSQXG) - Gitee.com】
联系---------→【邮箱:2784139418@qq.com】