动态文件版通讯录及C语言中的文件的读写操作

2023-11-06

上一期我们编写了一个C语言版本的简易通讯录,但是我们的之前的通讯录是没有记忆功能的,也就是说,一旦关闭了程序我们存储在里面的数据也就消失了。那么今天我们就来实现一个附带数据储存的通讯录。

在此之前,我们先来了解一下C语言中文件的读写函数:

1.fopen及fclose

fopen的作用是打开我们计算机储存的某个文件,函数返回值是FILE*类型,需要两个参数:1.文件路径 2.操作类型。下面我们来演示一下:

int main()
{
	FILE* pf = fopen("data.txt", "r");
	//打开文件
	if (pf == NULL)
	{
		perror("fopen");
		return -1;
	}
	//读文件
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

我们试着运行这段代码:

我们设置的错误函数提醒我们不存在该文件。

关于参数:

一.文件路径:我们在使用该函数的时候,打开文件所用的路径有两种:1.绝对路径 2.相对路径。

下面我们一一演示一下:

1.绝对路径:

int main()
{
	FILE* pf = fopen("C:\\Users\\win10\\Desktop\\data.txt", "r");
	//打开文件
	if (pf == NULL)
	{
		perror("fopen");
		return -1;
	}
	//读文件
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

 要注意使用绝对路径时,原本"\"处我们应再加上一条“\”,防止其变成转义字符导致路径失效。我们运行看看:

现在我们可以看到程序运行成功没有报错。

2.相对路径:相对路径是指将文件放置在源文件的文件夹内

int main()
{
	FILE* pf = fopen("data.txt", "r");
	//打开文件
	if (pf == NULL)
	{
		perror("fopen");
		return -1;
	}
	//读文件
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

运行结果与绝对路径一致。

二.操作符号

下面我们尝试在文件中写入一些我们想要的数据。

int main()
{
	FILE* pf = fopen("data.txt", "w");
	//打开文件
	if (pf == NULL)
	{
		perror("fopen");
		return -1;
	}
	//写文件
	fputc('h', pf);
	fputc('e', pf);
	fputc('l', pf);
	fputc('l', pf);
	fputc('o', pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

 可以注意到,我们使用的是相对路径,打开后的操作为写。写入数据的函数为fputs,该函数一次只能输入一个字符,第一个参数为想要输入的字符,第二个参数为先前打开文件返回的地址。下面我们来看一下运行结果:

看起来好像什么都没有发生,实际上:

打开文档的时候我们发现,我们想要的东西已经写入到了文档中。

int main()
{
	FILE* pf = fopen("data.txt", "w");
	//打开文件
	if (pf == NULL)
	{
		perror("fopen");
		return -1;
	}
	//读文件
	/*fputc('h', pf);
	fputc('e', pf);
	fputc('l', pf);
	fputc('l', pf);
	fputc('o', pf);*/
	fputs("hello", pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

 另外我们也可以使用fputs函数,一次可以输入一串字符串(如上图所示)。

实现了写入的方法后,我们不禁会去思考,既然可以把数据写入一个我们准备好的文档,那么我们是否也可以从那个文档中拿出我们想要的数据呢?答案是肯定的。下面我们就来读取我们刚刚写入到文档中的"hello"。

int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return -1;
	}
	int ch = getc(pf);
	printf("%c", ch);
	ch = getc(pf);
	printf("%c", ch);
	ch = getc(pf);
	printf("%c", ch);
	ch = getc(pf);
	printf("%c", ch);
	ch = getc(pf);
	printf("%c", ch);
	fclose(pf);
	pf = NULL;
	return 0;
}

我们首先使用fopen函数打开文件,然后使用命令"r",也就是读取命令。读取字符的函数我们使用的是getc函数。该函数每次可以读取一个字符,每读取一次就会向后跳动一个字符,因此我们使用五次就可以读取我们先前储存在文档中的单词了,没读取一次我们就将它打在屏幕上:

我们也可以通过fgets函数一次性读取一行的数据:

int main()
{
	char arr[20] = { 0 };
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return -1;
	}
	char* ch = fgets(arr,6,pf);
    printf("%s",arr);
	fclose(pf);
	pf = NULL;
	return 0;
}

 它的三个参数分别是:1.读取后储存的位置 2.读取的字符(n-1个,因此在设置参数时应比想要读取的字符要多一个)3.源数据地址。

我们可以看到这个函数也很好的实现了一样的功能。

下面我们来介绍一下今天用于改造通讯录的两个函数:fread,fwrite 

我们直接来看一下fwrite的用法:

struct S 
{
	int n;
	double x;
	char name[10];
};
int main()
{
	struct S a = { 10,3.14,"张三" };
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror(fopen);
		return -1;
	}
	fwrite(&a, sizeof(a), 1, pf);
	fclose(pf);
	pf = NULL;
	return 0;
}

该函数一共需要四个参数:1.数据地址 2.数据大小 3.每次写入的个数 4.写到什么地方

程序运行结果:

该函数的写入方式为二进制,所以我们未必可以直接读。

下面我们用fread来读取一下: 

struct S 
{
	int n;
	double x;
	char name[10];
};
int main()
{
	//struct S a = { 10,3.14,"张三" };
	struct S s = { 0 };
	FILE* pf = fopen("data.txt", "wb");
	if (pf == NULL)
	{
		perror(fopen);
		return -1;
	}
	//fwrite(&a, sizeof(a), 1, pf);
	fread(&s, sizeof(struct S), 1, pf);
	printf("%d %lf %s",s.n, s.x, s.name);
	fclose(pf);
	pf = NULL;
	return 0;
}

运行结果:

知道这些函数的作用和用法之后我们就可以开始对通讯录进行改造了!

首先,我们先前的通讯录的储存版本是1000个人的数据,但是在实际生活当中,因为每个人的情况不一样我们是用不了那么大的容量的。那么我们首先来实现通讯录的动态版本!

首先我们要改造的是存放数据的空间,原先我们是创造了一个结构体,在结构体内定义了一个结构体数组,数组元素个数是1000,以及一个用来记录储存人数的整形。

那么如果我们想要一个可有随储存人数变化而变化的空间,那么我们就需要以下几步:1.开辟初始空间 2.检查空间是否充足 3.充足(存入数据);不充足(开辟新空间)4.存入新数据。

那么我们首先来改造储存空间也就是通讯录结构体:

静态版本
//struct contact
//{
//	struct peoinfo data [max];//100人信息存放在数组中
//	int sz;//统计存放的人数
//};

动态版本
struct contact
{
	struct peoinfo* data;
	int sz;//统计存放的人数
	int capacity;//有效容量
};

我们将原本的结构体数组改为一个结构体指针,以此来维护用以储存个人信息的空间。同时我们增加了一个整形变量capacity,它代表的是有效的容量,在后期增加人数的时候,当有效容量等于储存人数的时候我们就需要开辟新的空间用以储存新的人员信息。

当储存空间开辟方式发生变化,那么初始化函数也应当随之变化:

///静态版本
//void initcontact(struct contact* con)
//{
//	con->sz = 0;
//	memset(con->date,0,sizeof(struct peoinfo));
//	//memset(con->date, 0, sizeof(con->date));
//}


//动态版本
void initcontact(struct contact* con)
{
	con->sz = 0;
	con->data = (struct peoinfo* )malloc(def * sizeof(struct peoinfo ));//开辟空间
	con->capacity = def;
}

原本静态的初始化函数只是将sz,储存信息都初始化为0,在动态版本中,我们设置初始化容量为3(原本是1000),同时使用malloc函数将空间开辟为能够储存三个人信息的大小同时将这块空间的指针转化为结构体类型传递给我们刚刚设置的data指针。

到这里,之前我们提到的三步我们完成了第一步,现在我们要开始第二步:2.检查空间是否充足 3.充足(存入数据);不充足(开辟新空间)

原本的静态增加联系人的函数我们只有判断空间是否充足+新增联系人两步。而现在我们要加入第三步:空间不足时开辟新的空间。

void checksize(struct contact* con)
	{
		if (con->sz == con->capacity)
		{
			struct peoinfo* ptr = (struct peoinfo*)realloc(con->data,
              (con->capacity + 2) * sizeof(struct peoinfo));
			if (ptr != NULL)
			{
				con->data = ptr;
				printf("增容成功!\n");
				con->capacity += 2;
			}
			else
			{
				exit(1);
			}
		}//检查容量
	}

首先我们封装一个判断函数,一进入该函数首先检查sz是否等于有效容量,如果容量不足我们就使用realloc函数开辟新的空间;需要注意的是,如果函数增容成功会返回一个非空指针,因此我们可以以此来判断是否增容成功,如果失败率则异常退出。

下面是完整的增加函数:

void modifycontact(struct contact* con)
	{
		int as = searcontact(con);
		if (as >= 0)
		{
			printf("请输入联系人姓名:");
			scanf("%s", con->data[con->sz].name);
			printf("请输入联系人年龄:");
			scanf("%d", &con->data[con->sz].age);
			printf("请输入联系人性别:");
			scanf("%s", con->data[con->sz].sex);
			printf("请输入联系人电话:");
			scanf("%s", con->data[con->sz].tele);
			printf("请输入联系人住址:");
			scanf("%s", con->data[con->sz].adr);
			printf("修改成功!\n");
			con->sz++;//储存人数加一
		}
	}

到这里我们的通讯录的空间就可以随着储存人数的增加而增加了,但是这不意味着就结束了。那些我们开辟用来存放数据的空间在程序结束以后应当重新释放掉,所以我们还有最后一步:销毁通讯录。

void destory(struct contact* con)
	{
		con->sz = 0;
		con->capacity = 0;
		free(con->data);
		con->data = NULL;
	}

我们用这个函数将人数和有效容量定义为0,同时释放掉我们之前开辟的空间放置内存泄漏。最后将data置为空指针。

到这里我们就将之前的通讯录成功改造成了动态内存的版本,但是这样说的通讯录依旧是不完美的,我们每次进入通讯录都需要重新录入信息才能使用,如果是这样这通讯录也就失去了它的价值,那么我们接下来将通讯录再次改造使他成为具有记忆功能。

实现记忆功能我们分为下面两大步:1.将之前输入的数据输入到我们实现准备好的文档 2.启动通讯录进行初始化后读取文档内的信息

下面我们实现第一步:将数据输入到文档中。

我们事先创建好一个TXT文档在源文件的文件夹当中,我们在结束时中加入写入函数:

case EXIT:
			//储存通讯录数据
			savecontact(&con);
			destory(&con);
			printf("退出通讯录!\n");
			break;		

下面是储存函数实现:

void savecontact(struct contact* con)
	{
		//打开文件
		FILE* pf = fopen("contact.txt", "wb");
		if (pf == NULL)
		{
			perror("savecontact::fopen");
			return;//结束函数
		}
		//写入数据
		int i = 0;
		for (i = 0; i < con->sz; i++)
		{
			fwrite(con->data+i, sizeof(struct contact), 1, pf);
		}
		//关闭文件
		fclose(pf);
		pf = NULL;
	}

通讯录有几个人的信息我们就写入几次,该功能的实现主要依靠fwirte函数,该函数的使用我们已经在开头介绍过就不在详述了。

第二步:初始化读取信息

void initcontact(struct contact* con)
{
	con->sz = 0;
	con->data = (struct peoinfo* )malloc(def * sizeof(struct peoinfo ));//开辟空间
	if (con->data == NULL)
	{
		perror(con->data);
		return;
	}
	con->capacity = def;
	//加载数据
	loadcontact(con);
}

我们在最后加入加载函数,下面是加载函数的代码:

void loadcontact(struct contact* con)
	{
		int i = 0;
		FILE* pf = fopen("contact.txt", "rb");
		if (pf == NULL)
		{
			printf("加载失败\n");
			return;
		}
		struct peoinfo tmp = { 0 };
		while (fread(&tmp, sizeof(struct peoinfo), 1, pf))
		{
			checksize(con);
			con->data[con->sz] = tmp;
			con->sz++;
			printf("加载成功!\n");
		}
		//关闭文件
		fclose(pf);
		pf = NULL;
	}

需要注意的是,我们每读取一个数据前都要检查一下容量是否充足,所以我们调用了之前写的判断函数。每读取一次,我们就将信息储存到我们用来保存信息的结构体中,同时有效容量和人数都++一次。最后加载完成关闭文件。

到这里,通讯录就改造完成了哦。喜欢的话就一键三连吧!谢谢大家!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

动态文件版通讯录及C语言中的文件的读写操作 的相关文章

随机推荐

  • 扩展欧几里得算法

    扩展欧几里得算法是啥 那就要先知道什么是欧几里得算法 欧几里得算法 扩展欧几里得算法是欧几里得算法的推广 利用欧几里得算法的思想和递归求得贝祖等式a x b y gcd a b 不定方程中的一组x和y的解 原理如下 设a gt b 当b 0
  • Coqui TTS 安装与测试

    前言 本篇记录一下 Coqui TTS 的安装 Coqui TTS 的主要作者是德国人 这个库似乎之前和 Mozilla 的 TTS https github com mozilla TTS 有千丝万缕的关系 但是现在后者的 TTS 已经停
  • 2021年江苏省职业院校技能大赛中职 “网络信息安全”赛项(超详细)

    2021年中职组 网络空间安全 赛项 一 江苏省竞赛任务书 二 任务书解析 三 不懂的可以私信博主 一 江苏省竞赛任务书 一 竞赛时间 8 00 11 00 共计3小时 二 竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 第 阶段
  • [附源码]计算机毕业设计Python课程在线测评系统(程序+源码+LW文档)

    该项目含有源码 文档 程序 数据库 配套开发软件 软件安装教程 项目运行 环境配置 Pychram社区版 python3 7 7 Mysql5 7 HBuilderX list pip Navicat11 Django nodejs 项目技
  • 一文理解pytorch张量概念和tensor的三种创建方式!

    1 张量是什么 张量是一个多维数组 它是标量 向量 矩阵的高维拓展 1 1 Variable Variable是 torch autograd中的数据类型 主要用于封装 Tensor 进行自动求导 data 被包装的Tensor grad
  • 超实用的IDEA插件推荐,百万级下载量

    超实用的30多款idea插件 有百万级下载量的优秀插件 你值得拥有 好的工具助你事半功倍 快速协助敲出更漂亮更有效率的代码 搬运工这里收集了很不错的IDEA插件 相信你一定会喜欢的 必备插件列表 Grep Console 自定义控制台输出格
  • C#常用代码

    最近学习用C 写了几个程序 记录一下常用的几个操作 方便以后Copy 文件操作 FileStream fs null try byte buf FileStream fs new FileStream strSampleFileName F
  • Ubuntu16.04下opencv2与ROSkinetic中自带opencv3不兼容问题总结

    1 背景 从ROSindigo换到ROSkinetic ROSkinetic中自带的opencv3 与原来indigo中opencv2不一样 所以原来的涉及opencv的程序都出了问题 最近这两天就一直在改兼容性 清明节最后一天了 总结一下
  • WebStorm2016.2 注册码及激活,2018.6.14亲测有效

    License server激活 这可能是最简单的了 在激活框 选择 License server 输入 http idea iteblog com key php 2018 6 14可用
  • table自定义表格的封装

    前言 对原生的table进行封装 让他满足我们一行显示不同个的需求 实现效果 如图所示 一行显示不同数量的内容 实现代码 1 封装的组件 custom table vue 源码看下面 1 一行显示几个td 2 表头数据 表格数据 3 js封
  • 深度学习python图像标记工具labelTool

    深度学习训练需要标记图像位置和类别 之前用的时候是叫做BBox Label Tool master 遇到大图像就显示不完整了 没有自适应缩放 这是改进后的Python脚本 目录结构 图片目录名images 标签目录名labels 图像目录下
  • JDK 新特性篇:JDK 8 新特性详解

    Java8新特性简介 Java 8 又称为 JDK 1 8 是 Java 语言开发的一个主要版本 Java 8 是 Oracle 公司于 2014 年 3 月发布 可以看成是自 Java 5 以来最具革命性的版本 Java 8 为 Java
  • 函数重载和缺省参数

    函数重载 C 允许在同一作用域类声明几个功能类似的同名函数 但是这些同名函数的形式参数 指参数的个数 类型或者顺序 必须不同 也就是说用同一个运算符完成不同的运算功能 函数重载必须是参数的个数或者类型不同 与返回值无关 在C语言当中是不支持
  • 李宏毅机器学习——梯度下降详细讲解

    文章目录 梯度下降 1 学习率 1 1自适应学习率 1 2 Adagrad算法 2 随机梯度下降 3 特征缩放 3 1 特征缩放原因 3 2 特征缩放方法 4 梯度下降的限制 梯度下降 梯度下降是为了解决回归方程中参数的最优化问题 即表现为
  • STM32F446+OLED12864做贪吃蛇游戏

    上面是我的微信和QQ群 欢迎新朋友的加入 闲暇之余发现看了下OLED12864的数据手册 发现他的显示在Y轴上只有页写 也就是一次写8个点 突然想到 如果自己程序需要精准定位到某个点 那这不是会很艹蛋么 去网上搜索 基本上都是页写的代码 也
  • mysql 自动备份(windows 环境)

    windows环境设置mysql自动备份 测试成功 要实现数据库的自动备份就需要一下两步 一 利用MySQL提供的备份命令mysqldump 结合Windows的任务计划程序 实现步骤 编写脚本 说明 该脚本不会关闭数据库 并且可以按每一天
  • C#强制杀进程

    通过cmd命令实现 using System using System Collections Generic using System Linq using System Text using System Threading Tasks
  • 小波矩特征提取matlab代码

    这是我上研究生时写的小波矩特征提取代码 新归一化方法小波矩特征提取 F imread a1 bmp F im2bw F F imresize F 128 128 求取最上点for i 1 128 for j 1 128 if F i j 1
  • AVPlayer 播放在线视频和本地文件的设置区别

    http blog csdn net u012671808 article details 30500595 comments
  • 动态文件版通讯录及C语言中的文件的读写操作

    上一期我们编写了一个C语言版本的简易通讯录 但是我们的之前的通讯录是没有记忆功能的 也就是说 一旦关闭了程序我们存储在里面的数据也就消失了 那么今天我们就来实现一个附带数据储存的通讯录 在此之前 我们先来了解一下C语言中文件的读写函数 1