C语言实现通讯录-动态版本与文件版本

C语言实现通讯录-动态版本与文件版本

1.前言

在先前的探索中,我构建了一个C语言实现简单的通讯录,它能够存储一定数量的联系人信息。然而,这个版本存在局限性------不仅联系人数目被固定,而且一旦程序关闭,所有的联系人信息都会丢失。

为了克服这些局限性,我决定开发一个更加灵活且持久的通讯录系统。在这篇文章中,我将介绍如何利用C语言中的动态内存分配函数(如realloc)来创建一个能够适应任意数量联系人的通讯录,即动态版本

并且还将展示如何使用文件操作来确保联系人信息即使在程序关闭后也能得以保存,即文件版本

2.动态版本

2.1联系人信息

之前的:

c 复制代码
#define MAX 100
#define NAME_MAX 20
#define TELE_MAX 12
struct Peo
{
	char name[NAME_MAX];
	int age;
	char tele[TELE_MAX];
};
struct Contact
{
	struct Peo peo[MAX];
	int num;
};

在原始版本中,我使用了一个固定大小的结构体数组 来存储联系人信息。这种设计虽然简单,但在实际应用中存在明显的不足之处,尤其是当需要存储的联系人数量不确定时。

为此,我转向使用动态内存分配技术来解决这一问题,因此,不能再用结构体数组,而改为结构体指针

且之前只准备了num来存储当前数量 ,在动态版本,还需一个变量存储当前最大数量,以免溢出。

改版:

c 复制代码
struct Contact
{
	struct Peo* peo;
	int num;
	int now_max;
};

我引入了一个指向Peo结构体指针peo,以及一个新的成员now_max来记录当前分配的最大联系人数量。

这样的改动使得我们的通讯录能够动态地适应不断增加的联系人数量。

2.2初始化

之前的:

c 复制代码
void init(struct contact* p)
{
	assert(p);
	p->num = 0;
	memset(p->peo, 0, sizeof(p->peo));
}

在静态版本中,我使用memset函数来初始化结构体数组中的每个元素。

然而,在动态版本中,我们需要采用不同的方法来进行初始化。

为此,我使用calloc函数来分配内存,并将所有分配的内存初始化为零。

改版:

c 复制代码
void Init(struct Contact* p)
{
	assert(p);
	p->num = 0;
	struct Peo*tmp = (struct Peo*)calloc(INIT_NUM, sizeof(struct Peo));
	if (!tmp)
	{
		perror("Init:calloc");
		return;
	}
	p->peo = tmp;
	p->now_max = INIT_NUM;
}

这里,我使用calloc函数来分配足够的内存空间,并将now_max初始化为INIT_NUM,即初始最大联系人数量。

这样的改动使得我们的通讯录在初始化时就能正确地分配所需的内存,并将所有成员初始化为零。

其中,INIT_NUM在头文件定义:

c 复制代码
#define INIT_NUM 3

perror
perror可打印错误信息,其头文件是<stdio.h>
"Init:calloc"可改为任意字符串,我这样写的目的是在错误信息打印后,知道是Init函数中的calloc步骤出了问题

可简单验证:

c 复制代码
#define INIT_NUM 10000000000000000

运行结果:

2.3自动扩容

在动态版本中,我们面临的一个重要挑战是如何有效地管理内存,尤其是在联系人数量增加时。

为此,我实现了一个自动扩容机制,当现有的内存空间不足以容纳新增加的联系人时,该机制会自动扩大内存容量。

即,联系人当前数量num与当前最大数量now_max相等时,自动扩容:

c 复制代码
void AddPeo(struct Contact* p)
{
	if (p->num == p->now_max)
	{
		struct Peo* tmp = (struct Peo*)realloc(p->peo, (p->now_max + ADD_NUM) * sizeof(struct Peo));
		if (!tmp)
		{
			perror("AddPeo:realloc");
			return;
		}
		p->peo = tmp;
		p->now_max += 2;
		printf("扩容成功\n");
	}
}

在这里,我使用realloc函数来重新分配更大的内存空间,并更新now_max以反映新的最大联系人数量。

通过这种方式,可以确保通讯录始终有足够的空间来存储新的联系人信息,而无需手动干预。

其中,ADD_NUM为扩充的联系人数量,在头文件定义:

c 复制代码
#define ADD_NUM 2

printf("扩容成功\n");可便于验证当前程序的正确性:

注:自动扩容函数 只需放入添加联系人函数,因为只有后者会增加联系人数量。

动态版本到此为止。


3.文件版本

3.1自动保存

除了动态管理内存之外,另一个重要的功能是确保联系人信息能够被持久化保存。为此,我实现了文件保存功能,能够在程序结束前自动将所有联系人信息保存到文件中:

c 复制代码
case 0:
	Ctrl_S(&con);//文件保存函数
	printf("exit\n");
	break;

使用Ctrl_S命名只为直观,而非我不懂'保存'的英文。

函数实现:

c 复制代码
void Ctrl_S(struct Contact* p)
{
	assert(p);
	FILE* fp = fopen("contact.txt", "wb");
	if (!fp)
	{
		perror("Ctrl_S:fopen");
		return;
	}
	for (int i = 0; i < p->num; i++)
	{
		fwrite(p->peo + i, sizeof(struct Peo), 1, fp);
	}
	fclose(fp);
	fp = NULL;
	printf("自动保存成功\n");
}

通过使用fwrite函数,我将每个联系人的信息写入到名为contact.txt的文件中。

这样,即使在程序关闭后,所有的联系人信息也能被安全地保存下来,供下次使用。

注:fwrite函数原型:size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );

效果:


需注意,由于是以二进制形式存储入文件,因此,以文本文档形式打开时会出现意义不明的乱码。

3.2打开时加载信息

函数实现:

c 复制代码
void Load(struct Contact* p)
{
	assert(p);
	FILE* fp = fopen("contact.txt", "rb");
	if (!fp)
	{
		perror("Load:fopen");
		return;
	}
	struct Peo tmp = { 0 };
	int i = 0;
	while (fread(&tmp, sizeof(struct Peo), 1, fp))
	{
		AddPeo(p);
		p->peo[i] = tmp;
		p->num++;
		i++;
	}
	fclose(fp);
	fp = NULL;
	printf("信息加载成功\n");
}

这里创建了一个临时的结构体 ,一次读一个联系人的信息,便于接收文件中的信息,并判断文件是否还有信息,如果没有,结束循环。

每次循环开始 ,需检查是否应该扩容

将此函数置于初始化函数 Init()的最后,即可运行程序。

注:fread函数原型:size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );


效果:

退出时:

打开时:


希望这篇博客能够帮助那些正在学习C语言或对内存管理和文件操作感兴趣的朋友!

本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!


相关文章:
C语言指针详解-上
C语言指针详解-下

相关推荐
在下不上天13 分钟前
Flume日志采集系统的部署,实现flume负载均衡,flume故障恢复
大数据·开发语言·python
陌小呆^O^27 分钟前
Cmakelist.txt之win-c-udp-client
c语言·开发语言·udp
I_Am_Me_43 分钟前
【JavaEE进阶】 JavaScript
开发语言·javascript·ecmascript
重生之我是数学王子1 小时前
QT基础 编码问题 定时器 事件 绘图事件 keyPressEvent QT5.12.3环境 C++实现
开发语言·c++·qt
Ai 编码助手1 小时前
使用php和Xunsearch提升音乐网站的歌曲搜索效果
开发语言·php
学习前端的小z1 小时前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
神仙别闹1 小时前
基于C#和Sql Server 2008实现的(WinForm)订单生成系统
开发语言·c#
XINGTECODE1 小时前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
zwjapple1 小时前
typescript里面正则的使用
开发语言·javascript·正则表达式
小五Five1 小时前
TypeScript项目中Axios的封装
开发语言·前端·javascript