C语言详解(文件操作)2

小羊在奋斗 2024-06-12 10:35:09 阅读 65

Hi~!这里是奋斗的小羊,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~

💥💥个人主页:奋斗的小羊

💥💥所属专栏:C语言

🚀本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为展示我的学习过程及理解。文笔、排版拙劣,望见谅。


目录

前言四、文件的顺序读写4.1 顺序读写函数介绍4.1.1 fputc4.1.2 fgetc4.1.3 fputs4.1.4 fgets4.1.5 fprintf4.1.6 fscanf4.1.7 sprintf(操作的不是文件)4.1.8 sscanf(操作的不是文件)4.1.9 fwrite4.1.10 fread 五、文件结束的判定5.1 被错误使用的feof5.2 文本文件读取结束5.3 二进制文件读取结束 六、文件缓冲区总结

前言

上篇文章中我们初步了解了文件的相关信息,文件的打开和关闭,以及文件的随机读写等

本篇文章将详细介绍一些文件顺序读写函数的作用、特点和用法,使我们更加方便地操作文件,还会讲到如何判定文件的结束等,内容可能有点多,请耐心看完哦


四、文件的顺序读写

4.1 顺序读写函数介绍

下面这些函数都在头文件<stdio.h>中定义

函数名 功能 适用于
fgetc 字符输入函数 所有输入流
fputc 字符输出函数 所有输出流
fgets 文本行输入函数 所有输入流
fputs 文本行输出函数 所有输出流
fscanf 格式化输出函数 所有输入流
fprintf 格式化输出函数 所有输出流
fread 二进制输入 文件输入流
fwrite 二进制输出 文件输出流

4.1.1 fputc

fputc函数的原型如下:

int fputc( int ch, FILE *stream );

fputc函数的功能是:写入字符ch到给定输出流stream

ch:要写入的字符stream: 输出流

开始时在当前工程目录底下创建一个文本文档,存入数据:

请添加图片描述

运行下面的代码:

#include <stdio.h>int main(){ //打开文件FILE* pf = fopen("test.txt", "w");if (pf == NULL){ perror("fopen");return 1;}//写文件int i = 0;for (i = 'a'; i <= 'z'; i++){ //fputc函数一次只能写入一个字符fputc(i, pf);}//关闭文件fclose(pf);pf = NULL;return 0;}

运行上面的代码后查看文档,可以看到里面的内容已经被修改

请添加图片描述


4.1.2 fgetc

fgetc函数原型如下:

int fgetc( FILE *stream ); stream:读取字符的来源

fgetc函数读取正常时返回读取到的字符的ASCII码值,失败时返回EOF

运行下面的代码:

#include <stdio.h>int main(){ //打开文件FILE* pf = fopen("test.txt", "r");if (pf == NULL){ perror("fopen");return 1;}//读文件int ch = 0;//注意:为处理 EOF 需要 int 而非 charwhile ((ch = fgetc(pf)) != EOF){ printf("%c ", ch);}//关闭文件fclose(pf);pf = NULL;return 0;}

运行成功,在终端上打印出 ‘a’ ~ ‘z’:

请添加图片描述

fgetcfputc适用所有输入流和所有输出流,当然包括标准输入流stdin和标准输出流stdout

#include <stdio.h>int main(){ int ch = 0;ch = fgetc(stdin);//从键盘(标准输入流)上读取fputc(ch, stdout);//将字符输出(写)到屏幕(标准输出流)return 0;}

请添加图片描述


4.1.3 fputs

fputs函数原型如下:

int fputs( const char *str, FILE *stream );

fputs函数的功能是:将以NULL结尾的字符串str的每个字符写入到输出流stream,如同通过重复执行fputc,不将 str 的空字符串写入

运行下面的代码:

#include <stdio.h>int main(){ //打开文件FILE* pf = fopen("test.txt", "w");if (pf == NULL){ perror("fopen");return 1;}//写文件fputs("Are you ok?", pf);//关闭文件fclose(pf);pf = NULL;return 0;}

运行成功后查看文档,内容已经被重写:

请添加图片描述

fputs函数在写入字符串的时候是不主动换行的

#include <stdio.h>int main(){ //打开文件FILE* pf = fopen("test.txt", "w");if (pf == NULL){ perror("fopen");return 1;}//写文件fputs("Are you ok?", pf);fputs("What can I say? man.", pf);//关闭文件fclose(pf);pf = NULL;return 0;}

在这里插入图片描述


4.1.4 fgets

fgets函数原型如下:

char* fgets( char *str, int count, FILE *stream ); str:指向char型数组元素的指针count:写入的最大字符数(典型的为 str 的长度)stream:读取数据来源的文件

fgets函数的返回值:成功时为str,失败时为NULL

fgets函数的作用:

从给定文件流读取最多count-1个字符并将它们存储于str所指向的字符数组若文件尾出现或发现换行符则终止分析,后一情况下 str 将包含一个换行符若读入字符且无错误发生,则紧随str的最后一个字符后写入空字符'\0'

test.txt文档中的内容改为“abcdefghijklmnopq”:

在这里插入图片描述

调试下面的代码:

#include <stdio.h>int main(){ //打开文件FILE* pf = fopen("test.txt", "r");if (pf == NULL){ perror("fopen");return 1;}//读文件char str[20] = "xxxxxxxxxxxx";fgets(str, 5, pf);printf("%s\n", str);//关闭文件fclose(pf);pf = NULL;return 0;}

在这里插入图片描述

可以看到,虽然函数fgets确实在数组str中存入了5个字符,但是只读取了文档test.txt实际的4个字符存入数组str中,还有一个是字符‘\0’

也就是说当参数count给的值是5的时候,实际只从文件中读取4个字符

test.txt文档中的内容改为:

在这里插入图片描述

调试下面的代码:

#include <stdio.h>int main(){ //打开文件FILE* pf = fopen("test.txt", "r");if (pf == NULL){ perror("fopen");return 1;}//读文件char str[20] = "xxxxxxxxxxxx";fgets(str, 10, pf);printf("%s\n", str);//关闭文件fclose(pf);pf = NULL;return 0;}

在这里插入图片描述

可以看到,数组str中并没有存入我们预想的10个字符,所以fgets函数遇到换行符‘\n’会停止读取,并且将‘\n’也存入数组str

当然不管哪种情况最后都会补‘\0’

同样的,fgetsfputs也适用所有输入流和所有输出流,当然也包括标准输入流stdin和标准输出流stdout

#include <stdio.h>int main(){ char str[20] = { 0 };fgets(str, 20, stdin);fputs(str, stdout);return 0;}

在这里插入图片描述


4.1.5 fprintf

fprintf函数原型如下:

int fprintf( FILE *stream, const char *format, ... );

将结果写入输出流stream

对比printf

int printf( const char *format, ... );

将结果写入输出流stdout

可以看到fprintf函数比printf多了一个参数——文件指针

其中 ...表示可变参数列表,所以函数fprintf也可以有若干个参数

运行下面的代码:

#include <stdio.h>struct S{ char name[20];int age;double weight;};int main(){ struct S s = { "xiaoshuai", 25, 75.3 };//打开文件FILE* pf = fopen("test.txt", "w");if (pf == NULL){ perror("fopen");return 1;}//写文件fprintf(pf, "%s\n%d\n%.1lf\n", s.name, s.age, s.weight);//关闭文件fclose(pf);pf = NULL;return 0;}

请添加图片描述

可以看到,文档的内容已经被重写


4.1.6 fscanf

fscanf函数的原型如下:

int fscanf( FILE *stream, const char *format, ... );

对比scanf函数:

int scanf( const char *format, ... );

也是多了一个文件指针参数

运行下面的代码:

#include <stdio.h>struct S{ char name[20];int age;double weight;};int main(){ struct S s = { 0 };//打开文件FILE* pf = fopen("test.txt", "r");if (pf == NULL){ perror("fopen");return 1;}//读文件fscanf(pf, "%s %d %lf\n", s.name, &s.age, &s.weight);//从文件中读取的信息存到结构体s中//注意:从文件中读的时候不要用 %.1lf//s.name是数组名不需要加取地址操作符printf("%s\n%d\n%.1lf\n", s.name, s.age, s.weight);//打印在屏幕上//关闭文件fclose(pf);pf = NULL;return 0;}

请添加图片描述

当然也可以用fprintf打印:

在这里插入图片描述

fprintffscanf就相当于printfscanf的升级版,功能是在它们两个之上的


4.1.7 sprintf(操作的不是文件

注意:函数sprintf操作的不是文件,在这里介绍是为了对比

sprintf函数原型如下:

int sprintf( char *buffer, const char *format, ... );

sprintf函数的功能:将结果写入字符串buffer, 如果所写入的字符串(加上终止空字符)超出由 buffer所指向的数组的大小,则行为未定义。

运行下面的代码:

#include <stdio.h>struct S{ char name[20];int age;double weight;};int main(){ char str[100] = { 0 };struct S s = { "xiaomei", 24, 55.2 };sprintf(str, "%s %d %.1lf", s.name, s.age, s.weight);printf("%s\n", str);return 0;}

在这里插入图片描述

sprintf其实是将格式化的数据转化为字符串


4.1.8 sscanf(操作的不是文件

注意:函数sscanf操作的不是文件,在这里介绍是为了对比

sscanf函数的原型如下:

int sscanf( const char *buffer, const char *format, ... );

sscanf函数的功能是从字符数组中提取数据,然后格式化

运行下面的代码:

#include <stdio.h>struct S{ char name[20];int age;double weight;};int main(){ char str[100] = { 0 };struct S s = { "xiaomei", 24, 55.2 };//将结构体变量s中格式化的数据转化为字符串存入字符数组str中sprintf(str, "%s %d %.1lf", s.name, s.age, s.weight);//printf("%s\n", str);//临时变量struct S tmp = { 0 };//将字符数组str中的数据格式化的存入结构体变量tmp中sscanf(str, "%s%d%lf", tmp.name, &tmp.age, &tmp.weight);fprintf(stdout, "%s %d %.1lf", tmp.name, tmp.age, tmp.weight);return 0;}

请添加图片描述


scanf/printf针对标准输入流 / 标准输出流的格式化输入 / 输出函数fscanf/fprintf针对所有输入流 / 所有输出流的格式化输入 / 输出函数sscanf/sprintf将格式化的数据转换为字符串 / 将字符串转化为格式化的数据

4.1.9 fwrite

fwrite函数原型如下:

size_t fwrite( const void *buffer, size_t size, size_t count,FILE *stream );

fwrite函数的参数:

buffer:指向数组中要被写入的首个对象的指针size:每个对象的大小count:要被写入的对象数stream:指向输出流的指针

fwrite函数的返回值:成功写入的对象数,若错误发生则可能小于count

fwrite函数的作用:将buffer指向空间内count个大小为size的元素数据写到输出流stream文件)中

运行下面的代码:

#include <stdio.h>struct S{ char name[20];int age;double weight;};int main(){ struct S s = { "xiaochou", 23, 65.7 };//打开文件FILE* pf = fopen("test.txt", "wb");if (pf == NULL){ perror("fopen");return 1;}//写文件//以二进制的形式写到文件中fwrite(&s, sizeof(struct S), 1, pf);//关闭文件fclose(pf);pf = NULL;return 0;}

请添加图片描述

运行成功后文档内是我们看不懂的二进制的信息


4.1.10 fread

fread函数的原型如下:

size_t fread( void *buffer, size_t size, size_t count,FILE *stream );

fread函数的参数:

buffer:指向要读取的数组中首个对象的指针size:每个对象的字节大小count:要读取的对象数stream:读取来源的输入文件

fread函数的返回值:成功读取的对象数,若出现错误或文件尾条件,则可能小于count

fread函数的作用:从输入流stream(文件)中读取count个大小为size个字节的数据存到buffer指向的空间内

运行下面的代码:

#include <stdio.h>struct S{ char name[20];int age;double weight;};int main(){ struct S s = { 0 };//打开文件FILE* pf = fopen("test.txt", "rb");if (pf == NULL){ perror("fopen");return 1;}//写文件//读取二进制的信息到文件中fread(&s, sizeof(struct S), 1, pf);//打印结构s的成员fprintf(stdout, "%s\n%d\n%.1lf\n", s.name, s.age, s.weight);//关闭文件fclose(pf);pf = NULL;return 0;}

请添加图片描述

可以看到fread函数又将文件中的二进制信息读到了结构体中变成了我们可以看懂的信息


五、文件结束的判定

5.1 被错误使用的feof

文件读取结束有两个原因:

遇到文件结尾 遇到错误

feof函数的原型如下:

int feof( FILE *stream );

feof函数的返回值:若已抵达流尾则为非零值,否则为 ​0​

feof函数的作用是:当文件已经读取结束的时候,判断读取结束的原因是否是遇到文件结尾

但是这个函数经常被用错,部分人以为feof函数的作用是判断文件读取是否结束,其实不是的


5.2 文本文件读取结束

文本文件读取是否结束,判断返回值:

fgetc:判断是否为EOFfgets:判断是否为NULL

例如:

#include <stdio.h>int main(){ //打开文件FILE* pf = fopen("test.txt", "r");if (pf == NULL){ perror("fopen");return 1;}//读文件int ch = 0;//注意:为处理 EOF 需要 int 而非 charwhile ((ch = fgetc(pf)) != EOF){ printf("%c ", ch);}//判断是什么原因结束的if (ferror(pf)){ puts("I/O error when reading");}else if (feof(pf)){ puts("End of file reached successfully");}//关闭文件fclose(pf);pf = NULL;return 0;}


5.3 二进制文件读取结束

二进制文件读取是否结束,判断返回值是否小于实际要读的个数。

例如:

#include <stdio.h>struct S{ char name[20];int age;double weight;};int main(){ struct S s = { 0 };//打开文件FILE* pf = fopen("test.txt", "rb");if (pf == NULL){ perror("fopen");return 1;}//读取二进制的信息到文件中size_t num = fread(&s, sizeof(struct S), 1, pf);if (num == 1){ puts("Array read successfully, contents:");}else if (feof(pf)){ printf("Error reading test.bin: unexpected end of file\n");}else if (ferror(pf)){ perror("Error reading test.bin");}//关闭文件fclose(pf);pf = NULL;return 0;}


六、文件缓冲区

ANSIC标准采用“缓冲文件系统”处理数据文件的,所谓缓冲文件系统是指系统自动的在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。

如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区,充满缓冲区后再逐个地将数据送到程序数据区(程序变量等),缓冲区的大小根据C编译系统决定。


总结

文件读写函数在编程中具有非常重要的作用,能够帮助程序员实现数据的持久化存储、数据交换、日志记录、配置文件处理等功能,提高程序的灵活性、可维护性和可扩展性,从而提升整个程序的质量和效率


声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。