文件操作(2)

前言

上节我们学习了文件操作,因为文件操作的内容比较多,我把文件操作的博客拆分成两节来进行讲解,那么事不宜迟,我们正式的开始今天的学习

文件的顺序读写(2)

fprintf、fscanf函数的使用

fprintf是格式化的输出函数,具体使用方法如下:

通过比较,我们发现 fprintf 函数和 printf 函数的参数及其相像,仅仅在第一个参数上面有区别,fprintf 函数相较于 printf 函数多出了一个文件流的参数,下面我们来试着使用一下 fprintf 函数:

#include <stdio.h>

struct S
{
	char name[20];
	int age;
	float score;
};

int main(void)
{
	struct S s = { "张三",20,65.5f };
	//把s里面的数据存在文件中

	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	
	//写文件,以文本的形式写入
	fprintf(pf, "%s %d %f", s.name, s.age, s.score);

	fclose(pf);
	pf = NULL;

	return 0;
}

通过分析 test.txt 文件,我们可以知道:

写文件是以文本的形式写入的,而非二进制文件形式

我们可以通过 fprintf 函数将数据写入文件,那么我们应该用什么方式把文件读出来呢?

答案是使用 fscanf 函数,我们来看看 fscanf 函数的具体使用方式:

我们通过对比也可以知道:fscanf 函数和 scanf 函数参数相似度也很高,也只缺少一个流参数,下面我们来尝试使用一下:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

struct S
{
	char name[20];
	int age;
	float score;
};

int main(void)
{
	struct S s = { 0 };
	//在文件中提取数据放入s

	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	
	//读文件
	fscanf(pf, "%s %d %f", s.name, &s.age, &s.score);

	//打印在屏幕上
	printf("%s %d %f\n", s.name, s.age, s.score);
	
	fclose(pf);
	pf = NULL;

	return 0;
}

我们知道,fprintf 和 fscanf 函数适用于所有输出流和所有输入流,那么我们该怎么印证这一点呢?

我们可以通过尝试使用 fprintf 将数据输出到屏幕上打印,我们来尝试一下:

struct S
{
	char name[20];
	int age;
	float score;
};

int main(void)
{
	struct S s = { 0 };
	//在文件中提取数据放入s

	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//读文件
	fscanf(pf, "%s %d %f", s.name, &s.age, &s.score);

	//打印在屏幕上
	fprintf(stdout, "%s %d %f\n", s.name, s.age, s.score);

	fclose(pf);
	pf = NULL;

	return 0;
}

scanf / fscanf / sscanf   &&   printf / fprintf /sprintf 

下面我们来进行一组函数的对比:

scanf / fscanf / sscanf 

printf / fprintf /sprintf 

sscanf 函数和 sprintf 函数

我们来了解一下 sscanf 函数和 sprintf 函数:

sprintf 函数:将格式化的数据写入一个字符串,也就是说,把格式化的数据转化成字符串

struct S
{
	char name[20];
	int age;
	float score;
};

int main(void)
{
	char buf[200] = { 0 };
	struct S s = { "张三",20,65.5f };
	sprintf(buf, "%s %d %f", s.name, s.age, s.score);
	printf("%s\n", buf);
	return 0;
}

sscanf 函数:在字符串中读取格式化的数据

struct S
{
	char name[20];
	int age;
	float score;
};

int main(void)
{
	char buf[200] = { 0 };
	struct S s = { "张三",20,65.5f };
	sprintf(buf, "%s %d %f", s.name, s.age, s.score);
	printf("1:以字符串的形式打印 %s\n", buf);
	struct S t = { 0 };
	sscanf(buf, "%s %d %f", t.name, &t.age, &t.score);
	printf("2:按照格式打印 %s %d %f\n", t.name, t.age, t.score);

	return 0;
}

(因为两个函数的使用与 printf 函数和 scanf 函数差不多,所有没有做过多的讲解,仅仅呈现代码来带大家了解其使用过程,若不能够理解,可以自行去 http://cplusplus.com 进行更细致入微的了解)

函数对比

通过之前的学习,我们可以知道:

scanf 函数:从标准输入流(键盘)上读取格式化的数据

fscanf 函数:从所有输入流上读取格式化的数据

sscanf 函数:在字符串中读取格式化的数据

printf 函数:把数据以格式化的形式打印在标准输出流

fprintf 函数:把数据以格式化的形式打印在指定的输出流

sprintf 函数:将格式化的数据写入一个字符串,也就是说,把格式化的数据转化成字符串

fread 函数和 fwrite 函数

我们可以知道:

ptr 是指向需要写入进数据的数组

size 是写入数据类型所占用的字节,例如:整型数组的 size 就为 4

count 是写入元素的个数

stream 是流

具体使用方法如下:

int main(void)
{
	int arr[] = { 1,2,3,4,5 };
	
	FILE* pf = fopen("test.txt", "wb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//写数据
	int sz = sizeof(arr) / sizeof(arr[0]);
	fwrite(arr, sizeof(arr[0]), sz, pf);

	fclose(pf);
	pf = NULL;

	return 0;
}

我们此时打开 test.txt 文件,看看里面是否有我们写入的内容:

打开了 test.txt 文件,虽然里面存有了文件,但是我们发现里面存储的是乱码,这是为什么呢?

因为 fwrite 函数是针对二进制数据输出的函数,当我们用文本的形式打开时,就会显示乱码,当我们以二进制的形式读取文件,就能出现我们写入的数据,此时我们需要使用 fread 函数:

通过比较我们可以发现,fread 函数和 fwrite 函数的参数是如出一辙的,那么接下来我们使用 fread 函数将二进制文件里面的数据读出来:

int main(void)
{
	int arr[5] = { 0 };

	FILE* pf = fopen("test.txt", "rb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//读取二进制数据
	int sz = sizeof(arr) / sizeof(arr[0]);
	fread(arr, sizeof(arr[0]), sz, pf);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}

	fclose(pf);
	pf = NULL;

	return 0;
}

因为 fread 的返回值类型是size_t 那么 fread 函数一次会读取 count 个数据并且返回,如果数组里面的元素大于 count 个,那么 fread 函数将会多次读取,直到数组里面的元素全部被读取完,这样我们就可以更好的读取未知数组元数个数的数组里面的数据,因此我们可以这样改写代码:

int main(void)
{
	int arr[5] = { 0 };

	FILE* pf = fopen("test.txt", "rb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//读取二进制数据
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;

	while (fread(arr + i, sizeof(arr[0]), 1, pf))
	{
		printf("%d ", arr[i]);
	}
	

	fclose(pf);
	pf = NULL;

	return 0;
}

文件的随机读写

fseek

fseek 函数可以根据文件指针的偏移量和位置来定位文件指针,也就是定位文件的内容里面的光标

变量 offset 就是偏移量

变量 origin 就是文件的起始位置,其中文件的起始位置有三种:

SEEK_SET:是文件的起始位置

SEEK_CUR:是文件指针当前的位置

SEEK_END:是文件的末尾

下面我们来使用一下:

int main(void)
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//读文件
	int ch = fgetc(pf);
	printf("%c\n", ch);

	return 0;
}

首先是没有使用 fseek 函数的时候

每次顺序读取以后,光标都会自动向后移动一位

当我们使用 fseek 函数的时候,此时我们使用SEEK_CUR,并且偏移量为4

int main(void)
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//读文件
	int ch = fgetc(pf);
	printf("%c\n", ch);
	
	fseek(pf, 4, SEEK_CUR);
	
	ch = fgetc(pf);
	printf("%c\n", ch);

	return 0;
}

此时我们读到了f,我们还可以用其他方法读到f:

int main(void)
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//读文件
	int ch = fgetc(pf);
	printf("%c\n", ch);
	
	fseek(pf, 5, SEEK_SET);
	
	ch = fgetc(pf);
	printf("%c\n", ch);

	return 0;
}
​
int main(void)
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//读文件
	int ch = fgetc(pf);
	printf("%c\n", ch);
	
	fseek(pf, -4, SEEK_END);
	
	ch = fgetc(pf);
	printf("%c\n", ch);

	return 0;
}

​

这三种方法我们都可以得到f

ftell函数

ftell函数可以返回文件指针相对于起始位置的偏移量

int main(void)
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//读文件
	int ch = fgetc(pf);
	printf("%c\n", ch);
	
	fseek(pf, -4, SEEK_END);
	
	ch = fgetc(pf);
	printf("%c\n", ch);
	printf("%d\n", ftell(pf));

	return 0;
}

rewind函数

rewind 函数可以让文件指针回到文件的起始位置

int main(void)
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//读文件
	int ch = fgetc(pf);
	printf("%c\n", ch);
	
	fseek(pf, -4, SEEK_END);
	
	ch = fgetc(pf);
	printf("%c\n", ch);

	rewind(pf);
	ch = fgetc(pf);
	printf("%c\n", ch);

	return 0;
}

文件读取结束的判断

被错误使用的feof

我们在读取文件的时候,不能用eof函数的返回值来直接判断文件是否读取结束

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

文件读取结束有以下几种原因:

1.遇到了文件末尾-----feof

2.读取的时候发生错误-----ferror

当我们打开一个流的时候,每一个流上都有两个标记值:

1.是否遇到文件末尾---------feof

2.是否发生错误-----------ferror

文本文件在读取的时候,我们可以通过返回值来判断读取是否结束:

1.fgetc 判断返回值是否为EOF(如果读取正常,返回读取字符的ASCII码值,如果遇到文件末尾或者发生错误,则返回EOF)

2.fgets 判断返回值是否为 NULL(如果读取正常,返回存储字符串的字符数组的地址,如果遇到文件末尾或者读取错误,返回NULL)

3.二进制文件读取结束判断返回值是否小于实际要读的个数(fread) 

下面我们来运用所学的知识完成文件与文件间的内容拷贝:

int main(void)
{
	FILE* pfin = fopen("test1.txt", "r");
	if (pfin == NULL)
	{
		perror("fopen1");
		return 1;
	}

	FILE* pfout = fopen("test2.txt", "w");
	if (pfout == NULL)
	{
		fclose(pfin);
		perror("fopen2");
		return 1;
	}

	//读文件和写文件
	int ch = 0;
	while ((ch = fgetc(pfin)) != EOF)
	{
		fputc(ch, pfout);
	}

	fclose(pfin);
	pfin = NULL;
	fclose(pfout);
	pfout = NULL;
	

	return 0;
}

代码比较简单,仅仅只作演示,不作讲解

文件缓冲区

在数据从内存中写入硬盘的时候,需要经过文件缓冲区,下面是内存缓冲区的官方解释:

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

我们在把程序数据区的内容写到硬盘里面去的时候,我们需要先填满缓冲区,在缓冲区满了以后才会写入到硬盘里面去,若是没有内存缓冲区,系统效率就会很低,会频繁的被打断。fflash 函数可以刷新缓冲区,直接将缓冲区里面现有的代码写入硬盘中,下面我们来写一个代码来验证这一点:

#include <windows.h>
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;
}

(因为我是VS2022,没有办法演示,大家知道怎么使用以及文件缓冲区的概念就可以了)

所以我们可以得出以下结论:

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

结尾

那么文件操作的所有内容就到此结束了,谢谢您的浏览!!!

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

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

相关文章

深度学习理论基础(六)注意力机制

目录 深度学习中的注意力机制&#xff08;Attention Mechanism&#xff09;是一种模仿人类视觉和认知系统的方法&#xff0c;它允许神经网络在处理输入数据时集中注意力于相关的部分。通过引入注意力机制&#xff0c;神经网络能够自动地学习并选择性地关注输入中的重要信息&…

深度学习系列-python实现-初步学习构建神经网络

深度学习系列-python实现-初步学习构建神经网络 前言1.在Keras中加载MNIST数据集2.构建简单的神经网络模型3.训练模型4.模型的预测和评估5.总结 前言 在数字时代&#xff0c;数据已经成为了一种无处不在的资源。从商业分析到科学研究&#xff0c;从人工智能到机器学习&#xf…

FPGA高端项目:解码索尼IMX327 MIPI相机+图像缩放+视频拼接+HDMI输出,提供开发板+工程源码+技术支持

目录 1、前言免责声明 2、相关方案推荐本博主所有FPGA工程项目-->汇总目录我这里已有的 MIPI 编解码方案 3、本 MIPI CSI-RX IP 介绍4、个人 FPGA高端图像处理开发板简介5、详细设计方案设计原理框图IMX327 及其配置MIPI CSI RX图像 ISP 处理自研HLS图像缩放详解Video Mixer…

Folder Icons for Mac v1.8 激活版文件夹个性化图标修改软件

Folder Icons for Mac是一款Mac OS平台上的文件夹图标修改软件&#xff0c;同时也是一款非常有意思的系统美化软件。这款软件的主要功能是可以将Mac的默认文件夹图标更改为非常漂亮有趣的个性化图标。 软件下载&#xff1a;Folder Icons for Mac v1.8 激活版 以下是这款软件的一…

mac+python3+selenium 4

下载自己的版本 ChromeDriver - WebDriver for Chrome - Downloadshttps://chromedriver.chromium.org/downloads https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.jsonhttps://googlechromelabs.github.io/chrome-for-testing/k…

m2ts是什么文件格式?m2ts手机能播放吗?

大多数现代手机可以播放M2TS&#xff08;MPEG-2 Transport Stream&#xff09;格式的视频文件&#xff0c;但也取决于手机型号和操作系统。某些手机可能需要安装第三方播放器才能播放此格式的视频。如果您的手机无法直接播放M2TS文件&#xff0c;可以尝试使用视频转换工具将其转…

试过了,ChatGPT确实不用注册就可以使用了!

看到官网说不用登录也可以直接使用ChatGPT 我们来试一下 直接打开官网 默认是直接进入了chatgpt3.5的聊天界面 之前是默认进的登录页面 聊一下试试 直接回复了&#xff0c;目前属于未登录状态&#xff0c;挺好&#xff01; 来试下ChatGPT4 跳转到了登录页面 目前来看gpt4还…

Maven是什么? Maven的概念+作用

1.Maven的概念 Maven中文意思为“专家“、”内行“的意思&#xff0c;它是一个项目管理工具&#xff0c;可以对Java项目进行构建、依赖管理&#xff0c;通俗点 就是通过pom.xml文件的配置获取jar包不用手动的去添加jar包。 2.Maven的作用 对于大的工程&#xff0c;需要引用各…

Golang Context是什么

一、这篇文章我们简要讨论Golang的Context有什么用 1、首先说一下Context的基本作用&#xff0c;然后在讨论他的实现 (1)数据传递&#xff0c;子Context只能看到自己的和父Context的数据&#xff0c;子Context是不能看到孙Context添加的数据。 (2)父子协程的协同&#xff0c;比…

游戏引擎架构01__引擎架构图

根据游戏引擎架构预设的引擎架构来构建运行时引擎架构 ​

github本地仓库push到远程仓库

1.从远程仓库clone到本地 2.生成SSH秘钥&#xff0c;为push做准备 在Ubuntu命令行输入一下内容 [rootlocalhost ~]# ssh-keygen -t rsa < 建立密钥对&#xff0c;-t代表类型&#xff0c;有RSA和DSA两种 Generating public/private rsa key pair. Enter file in whi…

用于AGV物流机器人的爱普生陀螺仪传感器XV7000系列

适用于AGV物流机器人的爱普生陀螺仪传感器XV7000系列:XV7001BB&#xff0c;XV7011BB。以前我们都知道XV7001BB&#xff0c;XV7011BB适用于扫地机器人&#xff0c;其实对于AGV物流机器人来说&#xff0c;XV7000系列生陀螺仪传感器也是其中重要一环。AGV机器人又叫做AGV搬运机器人…

day02-SpringCloud02(Nacos、Feign、Gateway)

1.Nacos 配置管理 Nacos 除了可以做注册中心&#xff0c;同样可以做配置管理来使用。 1.1.统一配置管理 当微服务部署的实例越来越多&#xff0c;达到数十、数百时&#xff0c;逐个修改微服务配置就会让人抓狂&#xff0c;而且很容易出错。我们需要一种统一配置管理方案&#x…

2024第八届全国青少年无人机大赛暨中国航空航天科普展览会

2024第八届全国青少年无人机大赛暨中国航空航天科普展览会 邀请函 主办单位&#xff1a; 中国航空学会 重庆市南岸区人民政府 招商执行单位&#xff1a; 重庆港华展览有限公司 为更好的培养空航天产业人才&#xff0c;汇聚航空教育产业创新科技&#xff0c;丰富和完善航…

Springboot整合Milvus向量库

1. Milvus的Maven依赖&#xff0c; 配置如下 <dependency><groupId>io.milvus</groupId><artifactId>milvus-sdk-java</artifactId><version>2.3.4</version><exclusions><exclusion><artifactId>log4j-slf4j-imp…

JAVAEE之JavaScript

1.JavaScript JavaScript (简称 JS) 是世界上最流行的编程语言之一 是一个脚本语言, 通过解释器运行 主要在客户端(浏览器)上运行, 现在也可以基于 node.js 在服务器端运行. 脚本是什么&#xff1f; 脚本&#xff08;script&#xff09;是使用一种特定的描述性语言&#x…

【教程】Kotlin语言学习笔记(六)——泛型

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 【Kotlin语言学习】系列文章 第一章 《认识Kotlin》 第二章 《数据类型》 第三章 《数据容器》 第四章 《方法》 第五章 《L…

[Rust开发]用可视化案例讲Rust编程6.动态分发与最终封装

全系列合集 [Rust开发]用可视化案例讲Rust编程1.用Rust画个百度地图 [Rust开发]用可视化案例讲Rust编程2. 编码的核心组成&#xff1a;函数 [Rust开发]用可视化案例讲Rust编程3.函数分解与参数传递 [Rust开发]用可视化案例讲Rust编程4.用泛型和特性实现自适配shapefile的读取 […

git可视化工具

Gitkraken GitKraken 是一款专门用于管理和协作Git仓库的图形化界面工具。它拥有友好直观的界面&#xff0c;使得Git的操作变得更加简单易用&#xff0c;尤其适合那些不熟悉Git命令行的开发者。GitKraken提供了丰富的功能&#xff0c;如代码审查、分支管理、仓库克隆、提交、推…

rabbitmq死信交换机,死信队列使用

背景 对于核心业务需要保证消息必须正常消费&#xff0c;就必须考虑消费失败的场景&#xff0c;rabbitmq提供了以下三种消费失败处理机制 直接reject&#xff0c;丢弃消息&#xff08;默认&#xff09;返回nack&#xff0c;消息重新入队列将失败消息投递到指定的交换机 对于核…