当前位置: 首页 > news >正文

【通关文件操作(下)】--文件的顺序读写(续),sprintf和sscanf函数,文件的随机读写,文件缓冲区,更新文件

目录

四.文件的顺序读写(续) 

4.8--fwrite函数 

 4.9--fread函数

五.sprintf函数和sscanf函数

5.1--函数对比

5.2--sprintf函数

5.3--sscanf函数 

六.文件的随机读写

6.1--fseek函数

 6.2--ftell函数

6.3--rewind函数 

七.文件缓冲区 

7.1--fflush函数

八.更新文件


🔥个人主页:@草莓熊Lotso的个人主页

🎬作者简介:C++研发方向学习者

📖个人专栏:《C语言》

⭐️人生格言:生活是默默的坚持,毅力是永久的享受。


四.文件的顺序读写(续) 

--在上篇文章中,我们学习的函数都是以文本形式的,剩下还有两个函数是以二进制文件,直接看下图。

这里我们继续介绍后面两个函数

4.8--fwrite函数 

1.  size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );

功能:函数用于将数据块写入stream 指向的文件流中,是以2进制的形式写入的。

参数:
  • ptr :指向要写入的数据块的指针。
  • size :要写入的每个数据项的大小(以字节为单位)。
  • count :要写入的数据项的数量。
  • stream :指向 FILE 类型结构体的指针,指定了要写入数据的文件流。
返回值:
返回实际写入的数据项数量。如果发生错误,则返回值可能小于 count
使用注意事项:
  • 需要包含 <stdio.h> 头文件。
  • 在使用fwrite() 之前,需要确保文件已经以二进制可写方式打开。
  • fwrite() 通常用于⼆进制数据的写入,如果写入文本数据,请谨慎处理换行符和编码等问题。

代码演示: 

//fwrite函数演示
//结构体示例
#include<stdio.h>
struct S
{char name[20];int age;float score;
};
int main()
{FILE* ps = fopen("data.txt", "wb");if (ps == NULL){perror("fopen");return 1;}//写文件struct S s = {"zhaoxiehao",18,85.5f};if (fwrite(&s,sizeof(struct S),1, ps) != 1){perror("fwrite");return -1;}//关闭文件fclose(ps);ps == NULL;//避免变成野指针return 0;
}

//数组示例
#include<stdio.h>
int main()
{FILE* ps = fopen("data.txt", "wb");if (ps == NULL){perror("fopen");return 1;}//写文件int arr[] = { 1,2,3,4,5 };if (fwrite(arr,sizeof(int),5, ps) != 5){perror("fwrite");return -1;}//关闭文件fclose(ps);ps == NULL;//避免变成野指针return 0;
}

 4.9--fread函数

1.  size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );

功能:函数用于从 stream 指向的文件流中读取数据块,并将其存储到 ptr 指向的内存缓冲区中。

参数:
  • ptr :指向内存区域的指针,用于存储从文件中读取的数据。
  • size :要读取的每个数据块的大小(以字节为单位)。
  • count :要读取的数据块的数量。
  • stream :指向 FILE 类型结构体的指针,指定了要从中读取数据的文件流。
返回值:返回实际读取的数据块数量。
使用注意事项:
  • 需要包含 <stdio.h> 头文件。
  • 在使用fread() 之前,需要确保文件已经以二进制可读方式打开(上篇中提到过在vs上怎么打开)。
  • ptr 指向的内存区域必须足够大,以便存储指定数量和大小的数据块。
  • 如果 fread() 成功读取了指定数量的数据块,则返回值等于 count ;如果读取数量少于count ,则可能已经到达文件结尾或者发上了错误。
  • 在二进制文件读取时, fread() 是常⽤的函数,但对于文本文件读取,通常使用 fgets()或 fscanf()

 代码演示:

//fread函数演示
//文件里是之前写出来的结构体信息
#include<stdio.h>
struct S
{char name[20];int age;float score;
};
int main()
{FILE* ps = fopen("data.txt", "rb");if (ps == NULL){perror("fopen");return 1;}//写文件struct S s = {0};if (fread(&s, sizeof(struct S), 1, ps) != 1){if (feof(ps)){printf("遇到了文件末尾\n");}else if (ferror(ps)){printf("读取错误");}}else{printf("%s %d %f", s.name, s.age, s.score);}//关闭文件fclose(ps);ps == NULL;//避免变成野指针return 0;
}

能成功读取并打印在屏幕上: 


五.sprintf函数和sscanf函数

5.1--函数对比

我们通过一个表格来对比着看scanf/fscanf/sscanf,printf/fprintf/sprintf。

scanf针对标准输入(stdin)的格式化输入函数
printf针对标准输出(stdout)的格式化输出函数
fscanf针对所有输入流(可以是文件流,也可以是stdin)的格式化输入函数
fprintf针对所有输出流(可以是文件流,也可以是stdout)的格式化输出函数
sprintf将格式化的数据转换成字符串
sscanf从字符串中提取格式化的数据

前面学习了scanf/fscanf以及printf/fprintf函数,我们再来学习一下sprintf和sscanf函数 

5.2--sprintf函数

 1.  int sprintf ( char * str, const char * format, ... );

功能:
将格式化数据写入字符数组(字符串)。它类似于 printf ,但输出目标不是控制台或文件,
而是用户指定的内存缓冲区。常用于动态生成字符串、拼接数据或转换数据格式。简而言之就是将格式化的数据转换成一个字符串。
参数:
  • str :指向字符数组的指针,用于存储生成的字符串(需确保足够大以防止溢出)。
  • format :格式化字符串,定义输出格式(如 %d %f %s 等)。
  • ... :可变参数列表,提供与格式字符串中说明符对应的数据。
返回值:
成功时:返回写入 buffer 的字符数(不包括结尾的空字符 \0
失败时:返回负值。

代码演示: 

//sprintf函数
#include<stdio.h>
struct S
{char name[20];int age;float score;
};
int main()
{struct S s = { "zhaoxiehao",18,85.5f };char buf[200] = { 0 };sprintf(buf, "%s %f %d", s.name, s.score, s.age);printf("%s",buf);//只能按上面sprintf的顺序,打印成字符串形式return 0;
}

5.3--sscanf函数 

1. i nt sscanf ( const char * str, const char * format, ...);

功能:从字符串中读取格式化数据。它与 scanf 类似,但输入源是内存中的字符串而非控制台或文件。常用于解析字符串中的结构化数据(如提取数字、分割文本等)。

参数:

  • str :要解析的源字符串(输入数据来源)。
  • format :格式化字符串,定义如何解析数据(如 %d %f %s 等)。
  • ... :可变参数列表,提供存储数据的变量地址(需与格式字符串中的说明符匹配)
返回值:
  • 成功时:返回成功解析并赋值的参数数量(非负值)。
  • 失败或未匹配任何数据:若输入结束或解析失败,返回 EOF (通常是 -1 )。

代码演示:

//sscanf函数
#include<stdio.h>
struct S
{char name[20];int age;float score;
};
int main()
{struct S s = { "zhaoxiehao",18,85.5f };char buf[200] = { 0 };sprintf(buf, "%s %f %d", s.name, s.score, s.age);printf("%s\n", buf);//只能按上面sprintf的顺序来打印字符串//这里buf里面已经有了数据了struct S t = { 0 };sscanf(buf, "%s %f %d", t.name, &(t.score), &(t.age));printf("%s %d %f",t.name,t.age,t.score);//这里可以按printf里的顺序打印结构体的数据return 0;
}

我们还可以对比看看两次打印的顺序: 


六.文件的随机读写

6.1--fseek函数

1. int fseek ( FILE * stream, long int offset, int origin );

功能:根据文件指针的位置和偏移量来定位指针

参数:

  • stream:指向 FILE 类型结构体的指针,指定了要操作的文件流。
  • offset:表示偏移量,以字节为单位。它指定了从origin指定的位置开始,文件指针要移动的距离,可以是正数,负数,也可以是0。
  • origin:指定偏移量的起始位置,是一个整数常量,有以下三种取值情况:
  1. SEEK_SET:文件开头,此时offset表示从文件开头开始的偏移量。
  2. SEEK_CUR:当前文件指针位置,此时offset是相对于当前位置(光标所在位置)的偏移量。
  3. SEEK_END:文件末尾,此时offset是从文件末尾开始的偏移量,通常为负数用于倒着定位文件指针。

返回值:

  • 成功时返回0。
  • 失败时返回非0值,并设置errno来表示错误类型。

 代码演示:

//fseek函数
//在读文件时的运用,假设文件里是abcdefghi
#include<stdio.h>
int main()
{FILE* ps =fopen("data.txt","r");if (ps == NULL){perror("fopen");return 1;}//读文件int ch = fgetc(ps);printf("%c\n", ch);//a//打印g//fseek(ps, 6, SEEK_SET);//fseek(ps, -3, SEEK_END);fseek(ps, 5, SEEK_CUR);//这里因为,上面读取字符的时候光标向前//来到了a,所以偏移量是5ch = fgetc(ps);printf("%c\n", ch);//g//关闭文件fclose(ps);ps = NULL;return 0;
}

//fseek函数
//在写文件时运用
#include<stdio.h>
int main()
{FILE* ps = fopen("data.txt", "w");if (ps == NULL){perror("fopen");return 1;}//写文件fputs("abcdefghi", ps);//先写不然后面无法执行//把g修改成x,这里用fputs写完后光标在最后 fseek(ps, -3, SEEK_END);fputc('x', ps);//写'x'//关闭文件fclose(ps);ps = NULL;return 0;
}

 

 6.2--ftell函数

1.  long int ftell ( FILE * stream );

功能:返回文件指针相对于起始位置的偏移量

参数:stream:指向 FILE 类型结构体的指针,指定要操作的文件流,好获取该文件当前的指针位置。

返回值:

  • 成功时,返回文件指针相对于文件开头的字节数
  • 失败时,返回-1L,并设置errno来指示错误类型

代码演示: 

//ftell函数
//我们来计算一下文件的字节数
#include<stdio.h>
int main()
{FILE* ps = fopen("data.txt", "w");if (ps == NULL){perror("fopen");return 1;}//写文件fputs("abcdefghi", ps);//先写不然后面无法执行//把g修改成x,这里用fputs写完后光标在最后 fseek(ps, -3, SEEK_END);fputc('x', ps);//让光标来到最后,再算相对起始位置的偏移量fseek(ps, 0, SEEK_END);int c = ftell(ps);printf("%d", c);//关闭文件fclose(ps);ps = NULL;return 0;
}

6.3--rewind函数 

1.  void rewind ( FILE * stream );

功能:让文件指针(光标)的位置回到起始位置,等效于fseek(ps, 0, SEEK_SET);

参数:stream:指向 FILE 类型结构体的指针,指定要操作的文件流,通过这个参数,rewind函数能够知道是要对那个文件进行操作,从而将该文件的文件指针移动到文件开头位置

代码演示:

//rewind函数
//返回指针起始位置等效于fseek(ps, 0, SEEK_SET);
#include<stdio.h>
int main()
{FILE* ps = fopen("data.txt", "w");if (ps == NULL){perror("fopen");return 1;}//写文件fputs("abcdefghi", ps);//先写不然后面无法执行//把g修改成x ,这里用fputs写完后光标在最后fseek(ps, -3, SEEK_END);fputc('x', ps);//让光标来到最后,再算相对起始位置的偏移量fseek(ps, 0, SEEK_END);int c = ftell(ps);printf("%d", c);//让文件指针回起始位置,把a改成q;rewind(ps);fputc('q', ps);//关闭文件fclose(ps);ps = NULL;return 0;
}


七.文件缓冲区 

ANSI C 标准采用“缓冲文件系统” 处理数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每⼀个正在使用的文件开辟⼀块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。

7.1--fflush函数

 1.  int fflush ( FILE * stream ); 

功能:强制刷新参数 stream 指定流的缓冲区,确保数据写入底层设备。
  • 输出流:将缓冲区中未写入的数据立即写入文件。
  • 输入流:行为由具体实现决定,非C语言标准行为(可能清空输入缓冲区)
  • 参数为 NULL 时:刷新所有打开的输出流

参数: stream :指向文件流的指针(如 stdout 、文件指针等)

返回值:成功返回 0 ,失败返回 EOF
注意事项:
  1. 仅对输出流更新流(最后⼀次操作为输出)有明确刷新行为
  2. 输入流的刷新行为不可移植(如清空输入缓冲区是非标准特性)  
  3. 程序正常终止( exit )或调用 fclose 时会自动刷新,但程序崩溃时缓冲区数据可能丢失

代码演示: 

//fflush
//冲刷缓冲区
#include <stdio.h>
#include <windows.h>
//VS2022 WIN11环境测试int main()
{FILE* pf = fopen("test.txt", "w");fputs("abcdef", pf);//先将代码放在输出缓冲区printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");Sleep(10000);printf("刷新缓冲区\n");fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)//注:fflush 在高版本的VS上不能使用了printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");Sleep(10000);fclose(pf);//注:fclose在关闭文件的时候,也会刷新缓冲区pf = NULL;return 0;
}

刷新缓冲区之前与之后的文件变化如下:

 这里可以得出一个结论:

因为存在缓冲区,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件,如果不做,可能导致读写文件的问题。


八.更新文件

--在文件的打开模式中有三种方式值得注意,分别是:"r+","w+","a+",它们分别是啥意思呢?

行为"r+""w+""a+"
解释可读/可写可读/可写可读/可写
文件不存在时打开失败自动创建新文件自动创建新文件
文件存在时保留内容清空内容保留内容
初始文件指针位置文件开头文件开头文件末尾
写入是否覆盖原有数据是(可定位覆盖)是(内容已清空,从头写入)否(默认是在文件末尾写数据)
典型用途修改文件部分内容创建新文件或完全重写旧文件在文件末尾追加数据,比如记录日志

补充:"w"和"wb"这类的在文件存在时,也是先清空内容,再创建的新文件

关键要点:

  1. 在写完文件后,要继续读文件的时候,在读取之前一定要使用fflush()刷新文件缓冲区,或者使用fseek(),rewind()重新定位文件指示器的位置。
  2. 在读完文件后,需要继续写文件之前,使用fseek(),rewind()重新定位文件指示器的位置。 

代码演示: 

更新文件
int main()
{FILE* pf = fopen("test.txt", "w+");if (pf == NULL){perror("fopen");return 1;}//写文件fputs("abcdefghi", pf);fflush(pf);//刷新缓冲区//读文件//先回到初始位置fseek(pf, 0, SEEK_SET);//rewind(pf);也可以int ch = fgetc(pf);printf("%c\n", ch);//a//abcdefghi,把def改成xxx//上面读完一个后,光标来到a后面,也就是b前面//离d的偏移量是2;fseek(pf, 2, SEEK_CUR);fputs("xxx", pf);//abcxxxghifclose(pf);pf = NULL;return 0;
}


往期回顾:

【通关文件操作(上)】--文件的意义和概念,二进制文件和文本文件,文件的打开和关闭,文件的顺序读写

【C语言动态内存管理】--动态内存分配的意义,malloc和free,calloc和realloc,常见的动态内存的错误,动态内存经典笔试题分析,柔性数组,总结C/C++中程序内存区域划分

结语:本篇文章就到此结束了,继前面一篇文章后,在此篇文章中给大家分享了文件操作中的剩余部分,如文件的顺序读写(续),sprintf和sscanf函数,文件的随机读写,文件缓冲区,更新文件等知识点,后续会继续分享其它的内容,如果文章对你有帮助的话,欢迎评论,点赞,收藏加关注,感谢大家的支持。

相关文章:

  • 第4讲、Odoo 18 模块系统源码全解与架构深度剖析【modules】
  • Parsel深度解析:从入门到高阶的网页数据抓取艺术
  • Spring Boot整活指南:从Helo World到“真香”定律
  • KeePass安装与KeePass设置中文教程
  • 自扶正救生艇,乘风破浪,守护生命
  • 差分互连的串扰-信号与电源完整性
  • Linux---系统守护systemd(System Daemon)
  • IAR无法跳转定义,IARstm8跳转显示路径出错,系统库文件文件名后有[RO]
  • SOC-ESP32S3部分:17-I2C驱动实例-EEPROM温湿度传感器
  • Java开发经验——阿里巴巴编码规范实践解析6
  • Rust使用Cargo构建项目
  • 常见的分词算法
  • SAP BASIS常用事务代码ST06 操作系统监控
  • @ModelAttribute、@RequestBody、@RequestParam、@PathVariable 注解对比
  • VUE3+ts 实践记录
  • 【AI面试秘籍】| 第24期:Transformers / LLM的词表应该选多大?
  • 从本地到云端:Code App+SSH协议在iPad开发中的性能优化实战
  • pyinstaller 使用 控制台闪退解决办法
  • 文本预处理
  • ubuntu24.04与ubuntu22.04比,有什么新特性?
  • 温州网站建设制作/培训心得总结
  • 合肥网站推广优化/郑州网络推广代理顾问
  • 网站做友链/可以免费发广告的网站
  • 网站后台编辑/如何优化关键词搜索排名
  • 做公司网站怎么做/百度网址大全
  • 微信公众号二维码/关键词营销优化