【C语言】文件操作(2)(文件缓冲区和随机读取函数)

在这里插入图片描述

文章目录

  • 一、文件的随机读取函数
    • 1.fseek函数
    • 2.ftell函数
    • 3.rewind函数
  • 二、文件读取结束的判断
    • 1.被错误使用的feof
    • 2.判断文件读取结束的方法
    • 3.判断文件结束的原因
      • feof
      • ferror
      • 判断文件读取结束原因示例
  • 三、文件缓冲区

一、文件的随机读取函数

   在上一篇的文章中,我们讲到了文件顺序读取的各种函数,顺序读取也就是从开头读到结尾,没有选择,我们今天要讲的就是文件的随机读取
   也就是我们不必按照文件的顺序进行读写,可以通过一些函数更改读写的位置,从而实现我们所说的随机读写,接下来我们就来学习这些函数

1.fseek函数

   fseek函数用来定位文件内容的光标,光标默认在开头,如果读取了一个字符,那么光标就会往后面移动一位,而fseek函数可以通过偏移量来定位光标,然后我们就可以从定位的位置进行读写,我们来看看fseek的原型:

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

   如果函数定位成功,那么就会返回0,定位失败就会返回一个非0值
   它的第一个参数是我们要定位光标的流,第二个参数就是我们的偏移量,是一个长整型,它要根据我们的第三个参数来定,第三个参数origin可以是三个常量值,如下图:
在这里插入图片描述
   当它取SEEK_SET时表示,光标的偏移量要从文件开头开始计算,当它取SEEK_CUR时,光标的偏移量要从当前光标位置开始计算,当取SEEK_END时,光标的偏移量要从文件尾开始计算,我们来举一个例子说明:
   假设有一个文件中存储着abcde,现在光标的位置在a后面,如:

a | bcde

   我们想要获取字符d,那么就要把光标移动到d的前面,如下:

abc | de 

   那么这时我们就要计算偏移量,偏移量是针对第三个参数origin的不同取值的,当origin取SEEK_SET时,我们光标的偏移量要从文件开头开始计算,那么此时我们要把光标移动到d前面,偏移量就是3
   当origin取SEEK_CUR时,光标的偏移量就要从当前光标位置开始计算,那么此时我们要把光标移动到d前面,偏移量就是2
   当origin取SEEK_END时,光标的偏移量要从文件尾开始计算,那么此时我们要把光标移动到d前面,偏移量就是-2
   所以偏移量不是绝对的,要看fseek第三个参数的取值
   接下来我们就来看一段代码,尝试分析代码运行的结果:

#include <stdio.h>

int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fputs("This is an apple.", pf);
	fseek(pf, 9, SEEK_SET);
	fputs(" orange", pf);
	fclose(pf);
	pf = NULL;
	return 0;
}

   首先程序以写的方式打开了当前目录下的文本文件test.txt,然后写入了一句英文:

This is an apple.

   然后对文件里的光标位置做了更改,它的含义就是将光标移动到从文件开头计算,偏移量为9的位置,我们经过计算,应该在以下这个位置:

This is a|n apple.

   光标在a和n的中间,那么这时我们又进行了写入,写入了如下字符串:

 book

   要注意的是,book的前面有一个空格,所以我们写入时不要把这个空格忘记了,使用w写的时候,会覆盖之前的数据,所以空格会覆盖n,book会覆盖 app,所以写入之后,应该是这个样子的字符串:

This is a bookle.

   我们来运行一下这个程序,然后去看看我们的test.txt的内容和我们的预期是否相同,如下:
在这里插入图片描述
   可以看到代码的结果正如我们所料,但是我们还是有一些疑问,我们难道每一次都去数偏移量吗?有没有什么办法可以计算偏移量呢?就要看我们接下来要学习的函数:ftell了

2.ftell函数

   ftell函数的作用就是返回当前文件光标到文件开头的偏移量,我们来看看它的原型:

long int ftell ( FILE * stream );

   它的原型看起来也很好理解,参数就是我们要操作的流,返回值是长整型,返回的就是当前文件光标到文件开头的偏移量
   接下来我们直接来看例子,看看代码运行会发生什么:
   代码的前提是,当前目录下有一个test.txt文件,里面的内容是hello world!

#include <stdio.h>
int main()
{
	long size;
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	
	fseek(pf, 0, SEEK_END); 
	size = ftell(pf);
	fclose(pf);
	printf("Size of test.txt: %ld bytes.\n", size);
	pf = NULL;
	return 0;
}

   首先程序打开了文件test.txt,创建了一个长整型变量size,随后使用了fseek函数,我们要看得懂这句代码是什么意思,它的意思就是,将文件光标移动到离文件结尾偏移量为0的地方,实际上就是把光标移动到了文件末尾
   然后此时我们使用ftell函数算出文件开头到光标的偏移量,也就是文件开头到文件末尾的偏移量,那么算出来的将会是我们字符的个数,而一个字符占用一个字节,所以我们就间接算出来了文件内容的大小
   我们来看看代码运行结果:
在这里插入图片描述

3.rewind函数

   rewind函数的作用就比较简单了,就是把文件中的指针位置重置到文件开头,我们来看看它的原型:

void rewind ( FILE * stream );

   它的参数就是我们要操作的流,没有返回值,从原型看就可以发现它应该是一个很简单的函数,它的作用就是将文件光标移动到开头,然后我们可以重新在开头对文件进行读写
接下来我们直接上案例,来看看代码运行结果:

#include <stdio.h>
int main()
{
	int i;
	char buffer[27];
	FILE* pf = fopen("test.txt", "w+");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	for (i = 'A'; i <= 'Z'; i++)
	{
		fputc(n, pf);
	}
	rewind(pf);
	fread(buffer, 1, 26, pf);
	fclose(pf);
	pf = NULL;
	buffer[26] = '\0';
	printf(buffer);
	return 0;
}

   首先,程序以读写的方式打开了当前目录下的test.txt文件,然后将大写字母A到Z的字符写入到了我们的test.txt文件中,随后就到了我们的rewind函数,它直接就将我们的光标移动到了开头
   然后我们就又使用了fread函数将pf中的数据读了出来,然后关闭文件,打印了读出的数据
   现在唯一的问题是,我们之前讲的fread是对二进制文件进行操作,那么它能不能对普通文本文件进行操作呢?我们来看看代码的运行结果:
在这里插入图片描述

   可以看到代码成功把文件中的内容读出来了,说明fread既可以读取二进制文件和文本文件,这是为什么呢?我们可以在cplusplus.com这个链接下搜索这个函数,看看这个函数是如何解释的:
在这里插入图片描述
   可以看到fread是一个函数,它的原型我们也解释过,这里不多说了,我们可以看下一行加粗的字体,翻译过来就是,从流中读取数据块,看到这个解释我们就知道了,它读取时不是 只能读取二进制,而是可以读取数据块
   所以在传参时我们才要传元素个数和元素大小,而读取数据块就不会分它是文本文件还是二进制文件,函数也没有明确说只能读取二进制文件,只是它可以读取二进制文件而已
   而另一个函数fwrite和函数fread也是一样的,它既可以写入文本数据又可以写入二进制数据,因为它写入的时候也是按照数据块进行写入

二、文件读取结束的判断

1.被错误使用的feof

   牢记:在⽂件读取过程中,不能⽤feof函数的返回值直接来判断⽂件的读取是否结束,feof 的作⽤是:当⽂件读取结束的时候,判断是读取结束的原因是否是:遇到⽂件尾结束
   再通俗一点的说,feof使用的前提就是文件的读取已经结束了,它的作用就是在文件读取结束后判断文件是不是读到末尾结束,如果我们用它去判断文件读取是否结束,很明显是错误的

2.判断文件读取结束的方法

   所以我们对不同的文件,提供了不同的判断方法,如下:
(1)文本文件是否读取结束
   根据我们的读取函数的返回值来确定,在上一篇文章中我们就学过了文件读取函数,这里我们就不再多赘述,如果忘记可以翻看上一篇文章:【C语言】文件操作(1)(文件打开关闭和顺序读写函数的万字笔记)

  • 判断函数fgetc的返回值是否为EOF
  • 判断函数fgets的返回值是否为NULL

(2)二进制文件是否读取结束

  • fread判断返回值是否⼩于实际要读的个数

3.判断文件结束的原因

   刚刚我们学习了如何判断文件读取结束,那么文件读取结束了不一定就是正常的全部读取成功了,所以又会有正常读取结束和错误读取结束两种区别,正常读取结束就是文件读取到了文件末尾,错误读取就是因为某种原因读取出现错误了,没有读到文件末尾
   那么我们怎么判断文件是正常读取结束还是错误读取结束了呢?一般是使用feof函数和ferror函数来进行判断

feof

   feof函数我们在上面已经做了基本介绍,它的作用就是,在文件读取结束后,判断文件读取结束的原因是不是碰到了文件尾,我们来看看它的原型:

int feof ( FILE * stream );

   函数的参数是要操作的流,当文件是正常读取结束,也就是文件是因为读到末尾了而结束,就返回一个非0值,非正常读取结束就返回0

ferror

   ferror函数就是在文件读取结束后,用来判断文件是否是错误读取结束,和feof有点相似,只是判断的内容不同,我们来看看它的原型:

int ferror ( FILE * stream );

   它的参数也是要操作的流,如果文件是错误读取结束,那么就返回非0值,如果没有错误读取结束,也就是正常读取结束了,就返回0

判断文件读取结束原因示例

   我们刚刚学习了feof和ferror函数,现在我们就来使用它们来判断文件结束的原因,要注意一个前提:当前目录下有一个文件test.txt,里面的内容是hello world!,接下来我们来看看怎么把这两个函数运用在实战上:

#include <stdio.h>

int main()
{
	char arr[20];
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fgets(arr, 20, pf);
	printf("%s\n", arr);
	if (feof(pf))
	{
		printf("文件正常读取结束\n");
	}
	if (ferror(pf))
	{
		printf("文件错误读取结束\n");
		perror("读取失败原因");
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

   我们将读取到的字符串放在了arr中,然后我们来判断文件是否正常读取结束,如果正常读取结束就打印一下这句话,如果错误读取结束,那么就使用perror来打印一下读取失败的原因,最后我们来看看代码运行结果:
在这里插入图片描述

三、文件缓冲区

   当我们对文件写入数据后,如果程序还在进行,并且没有关闭文件,那么我们会发现,我们写入的内容居然没有立刻就出现在文件中,而一旦关闭文件后写入的内容才出现在文件中,这是为什么呢?
   这时我们就要引入文件缓冲区的概念了,ANSIC 标准采⽤“缓冲⽂件系统” 处理的数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为程序中每⼀个正在使⽤的⽂件开辟⼀块“⽂件缓冲区”
   从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输⼊到内存缓冲区,充满缓冲区后再从缓冲区逐个地将数据送到程序数据区(程序变量等)
   在文件中,有多种情况可以刷新缓冲区,将缓冲区的数据写入文件,我们这里就讲一下常用的三种情况

  1. 当缓冲区被装满后自动刷新缓冲区,将数据写入文件
  2. 当文件被关闭时,也会刷新缓冲区,将数据写入文件
  3. 使用fflush函数刷新缓冲区,它可以几乎不受限制的随时刷新缓冲区,使得缓冲区中的数据写入文件

   那么缓冲区具体有多大呢?这个是不确定的,要看编译器的具体实现

   今天的内容就分享到这里啦,也是终于把文件操作写完了,文件操作还是挺难的,所以如果有什么问题欢迎在评论区留言或者私信我
   bye~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/896477.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法笔记day07

1.最长回文子串 最长回文子串_牛客题霸_牛客网 算法思路&#xff1a; 使用中心扩散算法&#xff0c;枚举所有的中点&#xff0c;向两边扩散&#xff0c;一个中点需要枚举两次&#xff0c;一次当回文串是奇数另一次回文串是偶数的情况。 class Solution { public:int getLong…

JRT怎么从IRIS切换到PostGreSql库

1.执行M导出得到建库脚本文件 2.下载生成的脚本到本地D盘 3.修改驱动为PostGreSql 4.修改连接串 5.到PostGreSql里面创建一个jrtlis的数据库&#xff0c;模式为jrt 6.启动网站点击导入脚本按钮 导入完成了就可以正常使用PostGreSql库了

Linux 进程终止和进程等待

目录 0.前言 1. 进程终止 1.1 进程退出的场景 1.2 进程常见退出方法 1.2.1 正常退出 1.2.2 异常退出 2. 进程等待 2.1 进程等待的重要性 2.2 进程等待的方法 2.2.1 wait() 方法 2.2.2 waitpid() 方法 2.3 获取子进程 status 2.4 阻塞等待和非阻塞等待 2.4.1 阻塞等待 2.4.2 非阻…

萤石联名朱炳仁・铜推出“萤石・国礼大师”AI智能锁 共襄美好家生活

引言&#xff1a;当前&#xff0c;文化与科技正以前所未有的紧密程度相互融合&#xff0c;以人工智能为代表的智能科技的强势介入正推动非遗文化实现从创意策划、生产制造、传播方式乃至保存模式的全面革新&#xff0c;孕育着无限可能。 另一方面&#xff0c;当下智能锁行业竞…

传知代码-字里行间的背叛:博文出卖了你

代码以及视频讲解 本文所涉及所有资源均在传知代码平台可获取 你的博文透露了你内心的秘密 随着社交媒体和短视频行业的快速发展&#xff0c;来自文本、视频和音频的多模态数据爆发式增长。 同时&#xff0c;捕捉设备的广泛使用&#xff0c;加上其使用的简便性、移动能力和低…

界面控件DevExtreme中文教程 - 如何与Amazon S3和Azure Blob存储集成?

DevExtreme拥有高性能的HTML5 / JavaScript小部件集合&#xff0c;使您可以利用现代Web开发堆栈&#xff08;包括React&#xff0c;Angular&#xff0c;ASP.NET Core&#xff0c;jQuery&#xff0c;Knockout等&#xff09;构建交互式的Web应用程序。从Angular和Reac&#xff0c…

1. 安装框架

一、安装 Laravel 11 框架 按照官方文档直接下一步安装即可 1. 安装步骤 2. 执行数据库迁移 在.env文件中提前配置好数据库连接信息 php artisan migrate二、安装 Filament3.2 参考 中文文档 进行安装 1. 安装 拓展包 composer require filament/filament:"^3.2" -W…

【功能安全】相关项定义item definition

目录 01 item definition定义 02 相关项组成 03 相关项最佳实践 📖 推荐阅读 01 item definition定义 概念阶段的开发是以相关项定义(Item Definition)开始的,相关项定义是对系统的描述,此系统也是标准中安全要求应用的对象。 相关项定义目的: a) 在整车层面对相关…

C++ string(2)

文章目录 1.初识迭代器和范围for1.1迭代器1.2范围for1.3 aout关键字 2.字符串长度相关计算1.size 和 length2. capacity 和 reserve 3.例题演示1. [917. 仅仅反转字母 - 力扣&#xff08;LeetCode&#xff09;](https://leetcode.cn/problems/reverse-only-letters/description…

spring揭秘31-spring任务调度01-spring集成Quartz及JDKTimer定时器

文章目录 【README】【1】Quartz任务调度框架【1.1】Job调度任务【1.2】任务调度触发器Trigger【1.3】\*Quartz框架执行调度任务代码实践【1.3.1】硬编码执行Quartz调度任务【1.3.2】基于生产者模式执行quartz调度任务&#xff08;推荐&#xff09; 【2】spring集成Quartz【2.1…

查找与排序-选择排序

选择排序也是基于“比较”和“交换”两种操作来实现的排序方法 。 每一趟排序在待排序序列中选择关键字最小&#xff08;或最大&#xff09;的数据元素加入到排好序的序列前&#xff08;或后&#xff09;&#xff0c;直至所有元素排完为止。 一、简单选择排序 1.简单…

2024产品管理新风向:项目管理软件不懂敏捷开发?

一、产品管理与敏捷开发的紧密关联 产品管理和敏捷开发之间存在着紧密的关联&#xff0c;二者相互促进&#xff0c;共同为企业创造价值。 &#xff08;一&#xff09;敏捷开发为产品管理带来的优势 敏捷开发能够极大地加快产品上市速度。在传统的开发模式下&#xff0c;产品…

SAP 关于在交货单进行定价条件的确定简介

SAP 关于在交货单进行定价条件的确定简介 业务场景前台操作1、创建交货单2、创建交货单3、创建发票系统配置1、定义条件类型2、定义并分配定价过程3、定义交货的定价过程确定4、维护开票凭证的复制控制SAP交货单定价是针对销售交货单的价格计算过程,通常包括基本价格、折扣、附…

Java读取PDF后做知识库问答_SpringAI实现

​​​​​​​​​​​​​​ 核心思路&#xff1a; 简单来说&#xff0c;就是把PDF文件读取并向量化&#xff0c;然后放到向量存储里面&#xff0c;再通过大模型&#xff0c;来实现问答。 RAG&#xff08;检索增强生成&#xff09;介绍&#xff1a; 检索增强生成&#x…

数据结构——树、二叉树和森林间的转换

前言 介绍 &#x1f343;数据结构专区&#xff1a;数据结构 参考 该部分知识参考于《数据结构&#xff08;C语言版 第2版&#xff09;》129~130页 &#x1f308;每一个清晨&#xff0c;都是世界对你说的最温柔的早安&#xff1a;ૢ(≧▽≦)و✨ 目录 前言 1、基础知识 2…

Qml-Button的使用

Qml-Button的使用 Button属性 Button的继承关系&#xff1a; Button – AbstractButton – Control – Item; Button的属性主要继承于AbstractButton。AbstractButton属性主要如下&#xff1a; a.action:是一个Action类型属性&#xff0c;与QAction类似&#xff0c;用于提供快…

【论文解读系列】EdgeNAT: 高效边缘检测的 Transformer

代码&#xff1a; https://github.com/jhjie/edgenat 论文&#xff1a; https://arxiv.org/abs/2408.10527v1 论文 EdgeNAT: Transformer for Efficient Edge Detection 介绍了一种名为EdgeNAT的基于Transformer的边缘检测方法。 1. 背景与动机 EdgeNAT预测结果示例。(a, b)…

c语言基础程序——经典100道实例。

c语言基础程序——经典100道实例 001&#xff0c; 组无重复数字的数002&#xff0c;企业发放的奖金根据利润提成003&#xff0c;完全平方数004&#xff0c;判断当天是这一年的第几天005&#xff0c;三个数由小到大输出006&#xff0c;输出字母C图案007&#xff0c;特殊图案008&…

【Petri网导论学习笔记】Petri网导论入门学习(七) —— 1.5 并发与冲突

导航 1.5 并发与冲突1.5.1 并发定义 1.14定义 1.15 1.5.2 冲突定义 1.17 1.5.3 一般Petri网系统中的并发与冲突定义 1.18一般网系统中无冲撞概念阻塞&#xff08;有容量函数K的P/T系统&#xff0c;类似于冲撞&#xff09;一般Petri网中并发与冲突共存情况 1.5 并发与冲突 Petr…

lstm基础知识

lstm前言 LSTM(Long short-term memory)通过刻意的设计来避免长期依赖问题&#xff0c;是一种特殊的RNN。长时间记住信息实际上是 LSTM 的默认行为&#xff0c;而不是需要努力学习的东西&#xff01; 在标准的RNN中&#xff0c;这个重复模块具有非常简单的结构&#xff0c;例…