文件的操作
前言:哈喽小伙伴们好久不见,国庆假期已经结束,接下来我们还是要马不停蹄的投入到学习当中,只有尽快调整状态回归学习,才能弯道超车。

今天我们一起来学习C语言——文件操作。
本篇文章讲到的所有函数均需要头文件#include<stdio.h>。
目录
一.什么是文件
1.程序文件
2.数据文件
3.文件名
二.为什么要使用文件
三.文件操作
1.文件指针
2.文件的打开与关闭
3.文件读写
(1)顺序读写
1)字符输入输出函数
2)文本行输入输出函数
3)格式化输入输出函数
4) 二进制输入输出函数
(2)随机读写
1)fseek
2)ftell
3)rewind
四.结语
一.什么是文件
我们电脑上磁盘中的各种文件就是我们今天所要讲的文件。
 
对于我们程序猿来说,从文件的功能角度来分析,我们一般所使用的文件有两种:
- 程序文件
- 数据文件
1.程序文件
包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)
2.数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件
那么本篇文章我们讲述的便是数据文件。
3.文件名
一个文件要有一个唯一的文件标识,以便于识别和辨认。
文件名包含三部分:文件路径 + 文件名主干 + 文件后缀
例如:c:\project\test.txt
c:\project 表示文件的当前路径
test 是文件名的主干
txt 是文件的后缀
为了方便,我们就将文件标识称为文件名。
二.为什么要使用文件
前边我们已经学习过了如何制作简易的通讯录,并讲解了如何通过动态内存函数来实现一个大小可变的通讯录。
但是现在我们又有问题了,因为我们实现的通讯录并不能够真正的保存信息。
当程序运行起来时,我们所输入的数据被保存在内存中,但是当我们退出程序时这些数据就会自动被内存所释放。
所以如果想要保存这些数据,以便于我们下次打开通讯录时这些数据依然存在,就要用到文件了。
三.文件操作
1.文件指针
对于文件的相关使用,我们有文件指针这样一个定义。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态以及文件当前的位置等)。
这些信息是保存在一个结构体变量中的,该结构体类型是由系统声明的,取名FILE。
同俗的来说,就是编译器系统内部已经帮你声明了一个结构体类型,这个结构体类型就被命名为文件,当你要对文件进行操作时,只需要用到FILE这个类型名就行,就类似于库函数的使用。
那么想要真正实现对文件的操作,就需要一个FILE类型的指针:
FILE* pf;//文件指针变量
通过这个指针来找到文件的信息区,并进行各种操作。
2.文件的打开与关闭
我们想要操作一个文件,必然就需要先打开它。
那么对于文件的打开,我们有一个专门的函数:

该函数有两个参数:
filename 是文件名,字符串形式输入
mode 是文件的打开方式,字符串形式输入
此外,这个函数会返回一个FILE*类型的指针来指向我们打开的这个文件,这样便建立起了文件与指针的关系。
既然有打开,那就一定会有关闭,关闭文件同样是通过一个函数:

这个函数只有一个参数:
stream 文件操作指针
通过指向文件的操作指针来关闭文件。
那么文件都有哪些打开方式呢???

这张表包含了所有的文件打开方式,下面我们就通过实际应用来讲解文件到底如何使用:
#include<stdio.h>
int main()
{//打开文件FILE* pf = fopen("test.txt", "w");if (pf == NULL){perror("fopen");return 1;}//操作文件//.....//关闭文件fclose(pf);pf = NULL;return 0;
}用fopen函数通过“w”(只写)文件的方式来打开一个名为“test.txt”的文件,并用pf指针来接收。
要注意的是,打开文件也是会出错的,如果出错,就会返回NULL空指针,所以我们一定要进行一次判断,判断pf指针是否为空,为空则通过perror函数报错,用return 1的方式来异常结束程序。
操作完文件之后,我们要将文件关闭,这时候便用到了fclose函数。
值得注意的是,文件关闭之后,pf指针就等于指向了一片未打开的文件信息区,这样pf就成为了野指针,所以要将pf指针及时置空。
运行程序, 通过“w”(只写)方式来打开文件,如果我们前面已经仔细看过了文件打开方式表,就会发现表的最后一列表示,如果文件不存在的话,就会新建一个文件。
此时如果你查看你的文件目录,就会发现我们创建了一个“test.txt”的文件。

如果我们一开始没有文件,想要通过“r”(只读)的方式来打开文件的话,就会报错:

很容易能够读出此英文含义为:没有这样的文件或者文件夹存在。
我们上述所打开的文件,是在我们当前的编译器的工程文件夹中,那如果我想打开一个其他位置的文件怎么办呢???
- 通过“..//”逐级往前
- 直接通过文件的绝对路径
例如:
fopen("..\\test.txt","w");
就是打开我们当前工程文件夹的上一级文件夹中的“tese.txt”文件,称为文件的相对路径,依次类推。
此外,我们还可以直接通过文件的绝对路径:
 
fopen("D:\\MyC\\nans-friends-c-language\\Project.88\\test.txt","w");
注意这里都要是双斜杠。
除此之外呢,我们还需要介绍一下输入,输出这两个概念:
当我们写文件,并将文件里的信息存储在硬盘上,这样称为输出。
反之,当我们调用硬盘上已有的文件信息,将它打开呈现,称为输入。
3.文件读写
那么我们学习完如何打开和关闭文件之后,就要紧接着来学习如何对文件进行读写啦。
首先要提醒大家一点,对于文件读写一定要注意“w”和“r”的切换。
文件的读写方式有两种:
顺序读写
随机读写
(1)顺序读写
文件的顺序读写有以下表格中的这些函数:

这些函数一共分为四类,下面我们就来逐一讲解。
1)字符输入输出函数
字符输入函数 fgetc
字符输出函数 fputc
字符输入输出函数,是针对单个字符的函数,一次只能操作一个字符。
 
先来看输出函数,它有两个参数:
character 要输出的字符
stream 文件操作指针
下面来看具体应用:
	//操作文件char ch;for(ch = 'a'; ch <= 'z'; ch++){fputc(ch, pf);}因为每次只能操作一个字符,所以我们用for循环将26个英文字母输出给文件,来看结果:

这样我们就算是程序退出,文件里的数据依然存在,真正的实现了数据的保存。
当然,如果你想要实现换行,空格这样的操作,用fputc函数也是能够实现的。
学习完怎么向文件输出之后,我们再接着来学怎么从文件输入。

这个函数只需要一个参数,也就是文件操作指针。
起初文件操作指针会指向字符的第一位,每读取一次之后,指针就会往后移一位。
当这个函数读取结束时,会返回EOF,所以我们也可以凭借这个来实现循环遍历。
既然是从外部输入数据,就需要东西来接收。
	//操作文件int ch = 0;while ((ch = fgetc(pf))!= EOF){printf("%c ", ch);}只要ch不是EOF,就一直遍历,得到结果如下:

2)文本行输入输出函数
顾名思义,这个函数会一次性的操作一行的数据。
fputs 文本行输出函数
fgets 文本行输入函数

先来看输出函数:
第一个参数是一个指针,指向我们所要操作的字符串的首地址,第二个依然是文件操作指针。
	//操作文件char arr[] = "hello";fputs(arr, pf);fputs("world\n", pf);fputs("!", pf);来看,第一个参数既然是字符串的首地址,那么我们自然就可以用数组。
值得注意的是,fputs函数也不会自动换行,必须手动换行。


当这个函数读取结束时,会返回一个NULL。
文本行输入函数相比于输出函数,多了一个num,也就是要输入的字符串大小。
但是实际上我们输入的字符串大小会比num少一个。
	//操作文件char arr1[100] = "0";char arr2[100] = "0";fgets(arr1, 100, pf);fgets(arr2, 3, pf);printf("%s", arr1);printf("%s", arr2);来看,用数组来接收字符串,我们的文本文件内容为:

得到结果如下:

能够看出,不管num有多大,都只会输出一行的字符,第二个结果也应证了实际输出的字符串大小是num - 1个。
那么我们前边说的这些个函数都是只能单一的操作字符,那么接下来,我们就来讲解一下能够操作结构体这种含有多种数据类型的函数。
3)格式化输入输出函数

先来看输出函数,第一个参数为文件操作指针,后边还可以有很多个参数,没有限制。
那么到底该如何使用呢???
#include<stdio.h>
struct S
{char c;int n;float f;
};
int main()
{//打开文件FILE* pf = fopen("test.txt", "w");if (pf == NULL){perror("fopen");return 1;}//操作文件struct S s = { 'a', 10, 3.14 };fprintf(pf, "%c %d %f", s.c, s.n, s.f);//关闭文件fclose(pf);pf = NULL;return 0;
}我们随便定义一个结构体类型,并输入一些数据。
大家有没有发现,实际上我们fprintf函数的参数传递和我们熟知的printf函数基本一模一样,只是多了一个pf文件操作指针。

不只是输出函数,输入函数亦是如此:

跟scanf函数相比,fscanf函数同样是多了一个文件操作指针pf。
#include<stdio.h>
struct S
{char c;int n;float f;
};
int main()
{//打开文件FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen");return 1;}//操作文件struct S s = {0};fscanf(pf, "%c %d %f", &(s.c), &(s.n), &(s.f));printf("%c %d %f", s.c, s.n, s.f);//关闭文件fclose(pf);pf = NULL;return 0;
}得到结果:

4) 二进制输入输出函数
所谓二进制输入输出,也就是将一组数据以二进制的形式在文件中进行读和写,此时我们的文件打开方式就要改为“rb”和“wb”啦。

我们依然是先来看输出函数,这个函数的参数就多了,分别是数据的首地址,单个数据的字节大小,数据的个数,以及字符操作指针。
#include<stdio.h>
int main()
{//打开文件FILE* pf = fopen("test.txt", "wb");if (pf == NULL){perror("fopen");return 1;}//操作文件int arr[] = { 1,2,3,4,5 };fwrite(arr, sizeof(arr[0]), sizeof(arr) / sizeof(arr[0]), pf);//关闭文件fclose(pf);pf = NULL;return 0;
}将arr数组的五个整型数据以二进制的形式输出到文件,结果如下:

因为是二进制,所以是这样的结果,那到底有没有成功呢???
我们通过二进制输入函数来判断:
 
能看出来二进制输入和输出两个函数的参数完全相同。
#include<stdio.h>
int main()
{//打开文件FILE* pf = fopen("test.txt", "rb");if (pf == NULL){perror("fopen");return 1;}//操作文件int arr[5] = {0};fread(arr, sizeof(arr[0]), sizeof(arr) / sizeof(arr[0]), pf);for (int i = 0; i < 5; i++){printf("%d ", arr[i]);}//关闭文件fclose(pf);pf = NULL;return 0;
}通过arr数组来接收二进制函数的输入值,并通过循环打印,得到结果如下:

可见我们的操作都是正确的。
这里要注意一点,fread函数读取失败时会返回0,读取成功则返回读取到的数据的个数。
至此,我们讲完了表中的所有文件操作函数。
但是小伙伴们有没有发现,我们讲的这些函数好像都只能从头开始按顺序操作。
那么接下来,我们就接着来学习不按顺序的文件操作函数。
(2)随机读写
关于文件的随机读写有以下三个函数需要我们掌握:
fseek
ftell
rewind
1)fseek

那么这个函数的作用是什么呢???我们根据它的参数来分析:
stream 我们已经很熟悉,是文件操作指针
offset 这个参数的类型是整型,它的英文含义是偏移量
origin 参数类型也是整型,英文含义为起始地
也就是说,这个函数可以帮助我们通过文件操作指针将文件内的操作系统光标移动到我们想要的起始地处,也可以从起始地的左右来回偏移。
那么对于起始地,我们只有一下三种规定的实际参数:

依次为文件的首地址,文件指针当前指向的地址和尾地址。
我们当前的文件内容为:

下面我们就来实际操作一下:
	//操作文件char ch = 0;fseek(pf, 4, SEEK_SET);ch = fgetc(pf);printf("%c", ch);来看,我们选择的起始地为文件的首地址,偏移量为正则为右,为负则为左,所以此时指针从a开始向右移动四位,指向e,在通过fgetc函数进行输出,结果如下:

现在我们的文件操作指针是指向了e,那么我们希望从e开始,去得到最后的g怎么做呢???
char ch = 0;
fseek(pf, 4, SEEK_SET);
fseek(pf, 2, SEEK_CUR);
ch = fgetc(pf);
printf("%c", ch);很简单,只需要在指针当前位置,在向右移动两位就可以啦:

SEEK_END同理我们这里就不在过多讲解啦。
2)ftell

这个函数就比较简单啦,它的作用是返回文件操作指针的当前位置相对于起始位置的偏移量。
	//操作文件char ch = 0;fseek(pf, 4, SEEK_SET);fseek(pf, 2, SEEK_CUR);int set = ftell(pf);printf("%d", set);我们利用上边的函数,此时我们的pf指向了‘g’,那么相对于‘a’的偏移量就是6。

3)rewind

这个函数同样简单,作用是将文件操作指针还原到文件的起始位置。
	//操作文件char ch = 0;fseek(pf, 4, SEEK_SET);rewind(pf);ch = fgetc(pf);printf("%c", ch);得到结果如下:

四.结语
关于文件操作的相关知识到这里就讲完啦。
国庆假期后的补课生活着实艰难,这篇文章我断断续续写了三天。

最后希望各位小伙伴们能够得到自己想要的知识。
最后不要忘记一键三连哦!!!
我们下期再见!
相关文章:
 
文件的操作
前言:哈喽小伙伴们好久不见,国庆假期已经结束,接下来我们还是要马不停蹄的投入到学习当中,只有尽快调整状态回归学习,才能弯道超车。 今天我们一起来学习C语言——文件操作。 本篇文章讲到的所有函数均需要头文件#inc…...
 
left join时筛选条件对查询结果的
-- 创建表 CREATE TABLE table1 (id int(11) NOT NULL AUTO_INCREMENT,card_num varchar(60) DEFAULT NULL,customer_id varchar(60) DEFAULT NULL,PRIMARY KEY (id) ) ENGINE InnoDBAUTO_INCREMENT 12DEFAULT CHARSET utf8mb4 COMMENT 测试表1;-- 创建表 CREAT…...
 
CVE-2020-9483 apache skywalking SQL注入漏洞
漏洞概述 当使用H2 / MySQL / TiDB作为Apache SkyWalking存储时,通过GraphQL协议查询元数据时,存在SQL注入漏洞,该漏洞允许访问未指定的数据。 Apache SkyWalking 6.0.0到6.6.0、7.0.0 H2 / MySQL / TiDB存储实现不使用适当的方法来设置SQL参…...
 
PaddleX解决分类、检测两大场景问题?实战精讲教程来了!
AI技术加速数字化进程,从制造、交通、能源等基础行业,到医疗、城市、零售、家居等与人们日常生活息息相关的行业,AI技术推动了数字化变革,也不断赋能于千行百业,但产业落地实践中依然面临着数据、算法等诸多困难。为了…...
Hive用户中文使用手册系列(二)
命令和 CLI 语言手册命令 命令是 non-SQL statements,例如设置 property 或添加资源。它们可以在 HiveQL 脚本中使用,也可以直接在CLI或Beeline中使用。 命令描述退出使用 quit 或 exit 退出交互式 shell。重启将 configuration 重置为默认值(从 Hive…...
 
2023年中国清净剂行业需求现状及前景分析[图]
清净剂用于中和由于燃烧和润滑油氧化产生的酸性物质,并清除颗粒和污物。这类杂质在油中的溶解度有限,因此,清净剂可以最大程度减少沉积物的生成,降低污染,提高环保排放标准。成熟产品有磺酸盐、硫化烷基酚盐、烷基水杨…...
 
文心一言 VS 讯飞星火 VS chatgpt (115)-- 算法导论10.2 8题
八、用go语言,说明如何在每个元素仅使用一个指针 x.np(而不是通常的两个指针 next和prev)的下实现双向链表。假设所有指针的值都可视为 k 位的整型数,且定义x.npx.next XOR x.prev,即x.nert和x.prev 的 k 位异或。(NIL 的值用0表示。)注意要说…...
 
Redis的BitMap实现分布式布隆过滤器
布隆过滤器(Bloom Filter)是一种高效的概率型数据结构,用于判断一个元素是否属于一个集合。它通过使用哈希函数和位数组来存储和查询数据,具有较快的插入和查询速度,并且占用空间相对较少。 引入依赖 <!--切面--&…...
【linux API分析】module_init
linux版本:4.19 module_init()与module_exit()用于驱动的加载,分别是驱动的入口与退出函数 module_init():内核启动时或动态插入模块时调用module_exit():驱动移除时调用 本篇文章介绍module_init() module_init() module_init…...
 
NSDT孪生编辑器助力智慧城市
技术有能力改变城市的运作方式,提高效率,为游客和居民提供更好的体验,实现更可持续的运营和更好的决策。 当今城市面临的主要挑战是什么,成为智慧城市如何帮助克服这些挑战? 我们生活在一个日益城市化的世界…...
 
如何优雅的实现接口统一调用
耦合问题 有些时候我们在进行接口调用的时候,比如说一个push推送接口,有可能会涉及到不同渠道的推送,以我目前业务场景为例,我做结算后端服务的,会与金蝶财务系统进行交互,那么我结算后端会涉及到多个结算…...
 
tomcat、nginx实现四层转发+七层代理+动静分离实验
实验环境: nginx1——20.0.0.11——客户端 静态页面: nginx2——20.0.0.21——代理服务器1 nginx3——20.0.0.31——代理服务器2 动态页面: tomcat1——20.0.0.12——后端服务器1 tomcat2——20.0.0.22——后端服务器2 实验步骤&…...
 
交通目标检测-行人车辆检测流量计数 - 计算机竞赛
文章目录 0 前言1\. 目标检测概况1.1 什么是目标检测?1.2 发展阶段 2\. 行人检测2.1 行人检测简介2.2 行人检测技术难点2.3 行人检测实现效果2.4 关键代码-训练过程 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 毕业设计…...
Java Excel转PDF,支持xlsx和xls两种格式, itextpdf【即取即用】
Java Excel转PDF itextpdf,即取即用 工具方法一、使用方式1、本地转换2、网络下载 二、pom依赖引入三、工具方法三、引文 本篇主要为工具方法整理,参考学习其他博主文章做了整理,方便使用。 工具方法 一、使用方式 1、本地转换 导入依赖创…...
 
重生奇迹mu宠物带来不一样的体验
重生奇迹mu宠物有什么作用? 全新版本中更是推出了各种宠物,在玩游戏时还可以带着宠物,一起疯狂的刷怪等等,可以为玩家带来非常不错的游戏体验,那么下面就来给大家说说各种宠物适合做什么事情。 1、强化恶魔适合刷怪 …...
 
【C++笔记】多态的原理、单继承和多继承关系的虚函数表、 override 和 final、抽象类、重载、覆盖(重写)、隐藏(重定义)的对比
1.final关键字 引出:设计一个不能被继承的类。有如下方法: class A { private:A(int a0):_a(a){} public:static A CreateOBj(int a0){return A(a);} protected:int _a; } //简介限制,子类构成函数无法调用父类构造函数初始化 //子类的构造…...
 
安装thinkphp6并使用多应用模式,解决提示路由不存在解决办法
1. 安装稳定版tp框架 composer create-project topthink/think tptp是安装完成的目录名称 ,可以根据自己需要修改。 如果你之前已经安装过,那么切换到你的应用根目录下面,然后执行下面的命令进行更新: composer update topthin…...
 
FPGA笔试
1、FPGA结构一般分为三部分:可编程逻辑块(CLB)、可编程I/O模块和可编程内部连线。 2 CPLD的内部连线为连续式布线互连结构,任意一对输入、输出端之间的延时是固定 ;FPGA的内部连线为分段式布线互连结构,各…...
 
Pytorch:cat、stack、squeeze、unsqueeze的用法
Pytorch:cat、stack、squeeze、unsqueeze的用法 torch.cat 在指定原有维度上链接传入的张量,所有传入的张量都必须是相同形状 torch.cat(tensors, dim0, *, outNone) → Tensor tensor:相同形状的tensor dim:链接张量的维度,不能超过传入张…...
聊聊HttpClient的RedirectStrategy
序 本文主要研究一下HttpClient的RedirectStrategy RedirectStrategy org/apache/http/client/RedirectStrategy.java public interface RedirectStrategy {/*** Determines if a request should be redirected to a new location* given the response from the target ser…...
 
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
 
装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
 
8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
 
学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
