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

C语言文件操作【基础知识 + 顺序读写 + 文件版通讯录】

全文目录

  • 😀 前言
  • 🤔 什么是文件
    • 😶 程序文件
    • 😶 数据文件
    • 😶 文件名
  • 🤨 文件指针
  • 🤫 文件的打开和关闭
    • 😑 `fopen` 打开文件
      • 📙 **mod的规律:**
    • 😑 `fclose` 关闭文件
      • 📙 文件打开不关闭的后果
  • 😵‍💫 文件的顺序读写
    • 🙄 **`fputc` 字符输出函数**
    • 🙄 **`fgetc` 字符输入函数**
    • 🙄 `fputs` 文本行输出函数
    • 🙄 **`fgets` 文本行输入函数**
    • 🙄 `fprintf` 格式化输出函数
    • 🙄 `fscanf` 格式化输入函数
    • 🙄 `fwrite` 二进制输出函数
    • 🙄 `fread` 二进制输入函数
  • 😏 三组 `scanf` 和 `printf` 函数对比
  • 😍 文件版通讯录
    • 🙂 将通讯录信息保存在文件中
    • 🙂 加载文件中的通讯录信息
  • 🌈 总结

😀 前言

前面写的程序都是一闪即逝,只要关闭了程序,就找不到运行的结果。如果想要将运行的结果保存下来,下次运行的时候接着上次运行,只有自己想要删除数据,数据才会消失。这就涉及到了数据持久化的问题, 我们一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据库等方式。

现在就学习一下使用文件,我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化。

🤔 什么是文件

文件是计算机文件属于文件的一种,与普通文件载体不同,计算机文件是以计算机硬盘为载体存储在计算机上的信息集合。文件可以是文本文档、图片、程序等等。文件通常具有三个字母的文件扩展名,用于指示文件类型(例如,图片文件常常以 JPEG 格式保存并且文件扩展名为 .jpg)。

但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)

😶 程序文件

程序文件存储的是程序,包括源程序和可执行程序。

包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。

😶 数据文件

数据文件比较多,数据的概念也比较广泛图形图像声音数字各种码制都是数据,存储这些数据的文件就是数据文件。

文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。

本文就是来学习一下数据文件。

以前我们写的代码所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。

但是有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上的文件。

在这里插入图片描述

😶 文件名

一个文件要有一个唯一的文件标识,以便用户识别和引用。

文件名包含3部分:文件路径+文件名主干+文件后缀

例如:c:\code\test.txt

为了方便起见,文件标识常被称为文件名

🤨 文件指针

文件类型指针简称文件指针

所谓的文件类型其实就是一个结构体类型:

每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE.

不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。

其中在VS2013提供的stdio.h中,文件类型的声明如下:

struct _iobuf 
{
	char *_ptr;
	int _cnt;
	char *_base;
	int _flag;
	int _file;
	int _charbuf;
	int _bufsiz;
	char *_tmpfname;
};
typedef struct _iobuf FILE;

在这里插入图片描述

每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节。

一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。结构体变量毕竟太大了。

// demo
FILE* pf; 	// 文件指针变量

定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。

在这里插入图片描述

🤫 文件的打开和关闭

使用文件之前需要打开文件,使用完要关闭文件(这个很重要,后面说)。

ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。

😑 fopen 打开文件

函数描述:
在这里插入图片描述

filename:文件名

文件名(路径 + 文件名)。如果没有指定路径,默认是在当前源文件的路径下(相对路径)。
注意: 路径的 \字符需要转义:\\

mod:打开文件的方式

在这里插入图片描述

📙 mod的规律:

文件不存在时:

如果是w都是创建新的文件,r都是出错,a 除了ab 都是创建新文件

文件存在时:

如果文件存在并且只是以w的形式打开文件(包括wb)。文件的内容会被清空

双重形态

读带上 + 就是读写, 追加和写带上 + 就是读写,否则都只能读或者写。

返回值 Return Value

如果文件被成功打开,函数将返回一个指向file对象的指针。

否则,将返回一个空指针。

在大多数库实现中,errno变量也设置为失败时的系统特定错误代码。

😑 fclose 关闭文件

函数描述:

在这里插入图片描述

关闭文件流stream

返回值Return Value

如果成功了返回0。

失败返回EOF

// demo
fclose(pf);
pf = NULL;

📙 文件打开不关闭的后果

1. 如果不关闭文件,可能会导致文件临时数据丢失,造成文件写入失败。因为文件数据在关闭之前不保存在磁盘,而是保存在运行内存中,关闭或者主动刷新才写入磁盘。

2. 还有文件没有关闭,会浪费系统的文件描述符的分配。

😵‍💫 文件的顺序读写

输出就是程序向文件里面写数据, 输入就是程序从文件里面读数据。读(输入)写(输出)都是以程序为参照物

顺序读写就是以文件从前往后依次读或写。

顺序读写函数:

在这里插入图片描述

适应所有输入流的函数,只需要将文件指针改为标准输入输出就可以实现标准的输入输出了。

🙄 fputc 字符输出函数

函数描述:

在这里插入图片描述

将字符character写入steam 指向的文件中。

返回值Return Value

写入成功返回被写入的字符。

写入失败返回EOF,并且设置错误码 。

// demo
for (char ch = 'a'; ch <= 'z'; ch++)
{
	fputc(ch, pf);
}

🙄 fgetc 字符输入函数

函数描述:

在这里插入图片描述

steam 指向的文件中读取一个字符。

返回值 Return Value

如果成功读取返回读取字符的ASCII码值。

如果读取到文件的末尾,返回EOF,并且为 steam 设置eof (end of file)

如果发生阅读错误同样返回EOF,但是会改为设置错误码。

// demo
char ch;
while ((ch = fgetc(pf)) != EOF)
{
	printf("%c", ch);
}

🙄 fputs 文本行输出函数

函数描述:

在这里插入图片描述

将字符串 str 输出到 steam指向的文件中。

返回值 Return Value

成功时,将返回非负值。

出错时,该函数返回 EOF 并设置错误指示器(ferror)。

// demo
fputs("qwertyuiop", pf); 	// 注意该函数不会主动添加\n

🙄 fgets 文本行输入函数

函数描述:

在这里插入图片描述

steam 指向的文件中读取字符并将其作为 C 字符串存储到 str中,直到读取 (num-1) 个字符或到达换行符或文件末尾,以先发生者为准。

返回值Return Value

成功后,函数返回 str

如果在尝试读取字符时遇到文件末尾,则设置 eof 指示器 (feof)。如果在读取任何字符之前发生这种情况,则返回的指针为空指针(str 的内容保持不变)。

如果发生读取错误,则设置错误指示器(ferror),并返回空指针(但str指向的内容可能已更改)。

// demo 
char str[256];
fgets(str, 256, pf);
printf("%s", str);

🙄 fprintf 格式化输出函数

函数描述:

在这里插入图片描述

将按格式指向流的 C 字符串写入流。如果 format 包含格式说明符(以 % 开头的子序列),则格式后面的其他参数将被格式化并插入到生成的字符串中,替换其各自的说明符。

format 参数之后,函数至少需要与格式指定的一样多的其他参数。

返回值Return Value

成功后,将返回写入的字符总数。

如果发生写入错误,则设置错误指示器(ferror)并返回负数。

如果在写入宽字符时发生多字节字符编码错误,errno 将设置为 EILSEQ 并返回负数。

文档中对于fprintf 的介绍巴拉巴拉一大堆,看着就头大。我们可以对比一下printf

在这里插入图片描述
可以看到 fprintf 对于 printf 只多了一个文件指针,所以使用fprintf 只需要比printf 多传一个文件指针就行了。

// demo
struct Student
{
	char name[10];
	int age;
	char phone[12];
} s = { "张三", 19, "1008611" };

fprintf(pf, "%s %d %s", s.name, s.age, s.phone);

🙄 fscanf 格式化输入函数

函数描述:

在这里插入图片描述

从流中读取数据,并根据参数格式将其存储到附加参数所指向的位置。

附加参数应指向已分配的对象,该对象的类型由格式字符串中相应的格式说明符指定。

返回值Return Value :

成功后,函数将返回成功填充的参数列表中的项数。此计数可能与预期的项数匹配,也可能由于匹配失败、读取错误或到达文件末尾而减少(甚至为零)。

如果在读取时发生读取错误或到达文件末尾,则会设置正确的指示器(feof或ferror)。

并且,如果在成功读取任何数据之前发生任何一种情况,则返回EOF
如果在解释宽字符时发生编码错误,函数会将errno设置为EILSEQ

同样的对比scanf

在这里插入图片描述

结果一目了然,只需要在使用scanf 的基础上加上一个文件指针就行了。

// demo
struct Student
{
	char name[10];
	int age;
	char phone[12];
} s;

fscanf(pf, "%s%d%s", &s.name, &s.age, &s.phone);

printf("%s %d %s\n", s.name, s.age, s.phone);

🙄 fwrite 二进制输出函数

函数描述:

在这里插入图片描述

从ptr指向的内存块向流stream中的当前位置写入count个元素的数组,每个元素的大小为size字节。

流的位置指示符提前写入的字节总数。

在内部,该函数将ptr指向的块解释为无符号char类型的(size*count)元素数组,并将它们按顺序写入流,就像为每个字节调用fputc一样。

返回值 Return Value :

返回成功写入的元素总数。

如果此数字与计数参数不同,则写入错误会阻止函数完成。在这种情况下,将为流设置错误指示器(ferror)。

如果大小或计数为零,则函数返回零,并且错误指示器保持不变。

// demo
struct Student
{
	char name[10];
	int age;
	char phone[12];
} s = { "张三", 19, "1008611" };

fwrite(&s, sizeof(s), 1, pf);

🙄 fread 二进制输入函数

函数描述:

在这里插入图片描述

从流中读取count个元素的数组,每个元素的大小为size字节,并将它们存储在ptr指定的内存块中。

流的位置指示符提前读取的字节总数。

如果成功,读取的字节总数为(size*conut)。

返回值Return Value

返回成功读取的元素总数。

如果此数字与count参数不同,则表示发生读取错误,或者读取时已到达文件末尾。在这两种情况下,都设置了适当的指示器,可以分别用ferrorfeof进行检查。

如果大小或计数为零,则函数返回零,并且ptr所指向的流状态和内容保持不变。

// demo
struct Student
{
	char name[10];
	int age;
	char phone[12];
} s;

fread(&s, sizeof(s),1, pf);
printf("%s %d %s\n", s.name, s.age, s.phone);

😏 三组 scanfprintf 函数对比

先来了解一下 sprintfsscanf

在这里插入图片描述

在这里插入图片描述

与格式化输出、输入函数百分之九十都是相似的,使用方法就不言而喻了。

然后总体来看一下这三组函数:

在这里插入图片描述

三组函数的参数都是基本相似的,只是使用的对象不同。

在这里插入图片描述

😍 文件版通讯录

有了上面的文件操作知识,我们就可以将之前写动态内存版本的通讯录再进一步的改善,只需要改动两个点:

  1. 打开通讯录时,需要加载磁盘上的通讯录信息
  2. 关闭通讯录时,需要将通讯录信息保存在磁盘上

这里使用的二进制的读写,因为是在想不出文本读写怎么操作(尴尬)。

🙂 将通讯录信息保存在文件中

// 将通讯录内容刷新到磁盘上
void SaveContact(const Contact* con)
{
	assert(con);

	// 文件打开
	FILE* pf = fopen("contact.dat", "wb");
	if (pf == NULL)
	{
		perror("SaveContact::fopen");
		return;
	}

	// 输出
	for (int i = 0; i < con->size; i++)
	{
		fwrite(con->data + i, sizeof(PeoInform), 1, pf);
	}

	// 关闭文件
	fclose(pf);
	pf = NULL;
}

🙂 加载文件中的通讯录信息

// 加载磁盘上的通讯录内容
void LoadContact(Contact* con)
{
	assert(con);

	// 打开文件
	FILE* pf = fopen("contact.dat", "rb");
	if (pf == NULL)
	{
		perror("LoadContact::fopen");
		return;
	}

	// 输入
	int i = 0;
	while (fread(con->data + i, sizeof(PeoInform), 1, pf))
	{
		CheckCapacity(con);
		i++, con->size++;
	}

	// 文件关闭
	fclose(pf);
	pf = NULL;
}

源码: 文件版通讯录

🌈 总结

文件操作在日后的编程中将会显得愈发重要,这些知识文件的冰山一角,所以一定要好好掌握这些知识点。

下一章: 文件的随机读写


http://www.kler.cn/news/16798.html

相关文章:

  • 【Java笔试强训 17】
  • maven install的时候报Unable to find main class
  • 财报解读:照明行业景气上行,欧普照明已步入增长“快车道”
  • Noah-MP陆面过程模型建模方法与站点、区域模拟实践技术
  • linux命令之tar详解
  • 快速了解车联网V2X通信
  • 接踵而至,昆仑万维天工大语言模型发布
  • Linux必会100个命令(六十)curl
  • 小满nestjs(第二十八章 nestjs 事务)
  • 1.软件测试
  • 常见元件、封装、尺寸、表面处理等
  • C语言中的三种语句
  • fastai2 实现SSD
  • 1699_simulink代码生成配置初级方案
  • 【Java】内部类Object类
  • SpringBoot整合Redis实现点赞、收藏功能
  • @TransactionalEventListener的使用和实现原理
  • 【五一创作】【Simulink】采用延时补偿的三相并网逆变器FCS-MPC
  • 如何在CentOS上详细安装PageOffice进行企业文档管理和协作
  • Java 基础入门篇(五)—— 面向对象编程
  • 05_从0运行,重定位,初始化,中断再到定时器
  • kafka单机配置
  • 探索三维世界【3】:Three.js 的 Geometry 几何体 与 Material 材质
  • 《QDebug 2023年4月》
  • 烟火识别智能监测系统 yolov5
  • 生物信息学中---数据集不平衡的处理方法
  • 小红书违禁词有哪些,小红书违禁词汇总分享
  • 来上海一个月的记录、思考和感悟
  • ffmpeg-mov-metadate不识别Bug修复
  • JUC多并发编程 Synchronized与锁升级