C语言进阶——升级版通讯录

简易版通讯录存在的问题

再过去的文章里,我们讲过了简易版通讯录。
简易版通讯录中,存在两个问题:

  1. 存放联系人信息的data数组中,存放的数据个数是被固定写死的,如果将来通讯录放满了,就放不下新的联系人了。
  2. 每次退出程序,数据并不会保存,这样无法达到我们保存联系人的目的。

接下来,我来带着大家解决上述提到的两个问题。

一、第一个问题

1. 改造通讯录结构体

之前我们是这样定义通讯录结构体的,data数组被写死,那我们该如何解决呢?

  1. 改造前的通讯录结构体:
typedef struct Contact  //定义通讯录的结构体
{
	PeoInfo data[100];
	int sz;
}Contact;

往期我们还讲过,如果需要动态申请内存空间,就需要使用动态内存管理函数。
动态申请的空间需要指针来维护,所以我们把原来的data数组,改成PeoInfo* 类型的data指针,这样待会就可以使用malloc函数动态申请空间,用data指针来维护,并且当通讯录需要扩容的时候,我们可以使用realloc函数调整malloc动态申请的空间的大小。

  1. 改造后的通讯录结构体:
typedef struct Contact  //定义通讯录的结构体
{
	PeoInfo* data;
	int sz;
	int capacity;
}Contact;

这里我们除了需要sz来记录当前通讯录存放的联系人的个数,我们还需要一个变量来存放通讯录当前能存放的联系人个数,也就是通讯录的容量,我们命名为capacity。
这样将来可以比较 sz 和 capacity 的大小来判断通讯录是否放满,是否需要扩容。

2.动态申请内存空间

我们已经知道,将动态申请的空间交给data指针维护,但是我们应该从哪里下手呢?
这是之前讲过的简易版通讯录的test函数中的代码,我们可以看到,先是创建了一个Contact类型的con变量,con的内部就有data指针,紧接着就调用了初始化函数,看来我们应该从初始化函数下手了。
在这里插入图片描述
之前我们是这样实现初始化函数的,在这里把data数组中的元素全部初始化为0,那么现在我们没有data数组了,只有data指针,并且data指针还没有初始化。
于是我们在这里动态开辟一块儿空间,交给data指针来维护就在合适不过了。
下面是初始化函数的动态版本:

//初始化通讯录  ------  静态版本
void InitContact(Contact* pc)
{
	assert(pc);//使用断言语句防止传入的pc指针为空指针
	memset(pc->data, 0, sizeof(pc->data));//使用memset函数,把con中的data数组全都初始化为0
	pc->sz = 0;//con中的sz初始化为0
}


//初始化通讯录  ------  动态版本
#define CAPACITY 3	//默认容量
void InitContact(Contact* pc)
{
	assert(pc);
	pc->data = (PeoInfo*)malloc(sizeof(PeoInfo) * CAPACITY);
	//动态开辟
	if (pc->data == NULL)//老规矩动态开辟空间,要记得检查是否开辟成功。
	{
		perror("InitContact");//如果开辟失败,perror打印错误信息
		//这里perror内部写InitContact,后续如果报错,就知道是是初始化函数这里动态开辟失败
		return;
	}
	//开辟成功
	pc->sz = 0;
	pc->capacity = CAPACITY;//将capacity变量赋值为默认大小3(CAPACITY)
}
  1. malloc函数开辟的空间大小的解释:
    我们开辟的空间未来要存放联系人的信息,类型为PeoInfo类型,所以开辟CAPACITY个PeoInfo类型的空间大小。
  2. #define 定义 CAPACITY为3,含义:通讯录的默认初始大小,用#define 定义方便后续修改。

至此我们就成功实现了:通讯录存放联系人的信息是动态开辟的。

3. 实现通讯录的扩容

我们只完成了空间是动态开辟的,但是这个空间大小依然是固定的,那么我们就需要用到realloc函数来调整已经申请的动态内存空间的大小。
我们需要思考一个问题,什么时候才需要扩容呢?也就是说,通讯录什么时候才会满呢?
答案就是,在增加联系人的时候,通讯录才会放满联系人。
那么看来我们只需要在增加联系人函数里面动手脚就好了。
下面这段代码,被注释掉的是简易版本的实现方式。

//增加联系人  ------  静态版本  放满不能再放
//void AddContact(Contact* pc)
//{
//	assert(pc);//我们不希望传入的pc指针为空,使用assert,使代码健壮性更强
//	if (pc->sz == DATA_MAX)
//	{
//		printf("通讯录已满,无法添加!\n");
//		return;
//	}
//	printf("请输入姓名:");
//	scanf("%s", pc->data[pc->sz].name);//name是一个数组名,表示地址,所以不需要取地址
//	printf("请输入年龄:");
//	scanf("%d", &(pc->data[pc->sz].age));//age为int类型,需要取地址
//	printf("请输入性别:");
//	scanf("%s", pc->data[pc->sz].sex);
//	printf("请输入电话:");
//	scanf("%s", pc->data[pc->sz].tel);
//	printf("请输入地址:");
//	scanf("%s", pc->data[pc->sz].addr);
//	printf("添加联系人成功!\n");
//	pc->sz++;
//}

#define ADD_CAPACITY 3		//默认每次增加的容量
//检查并增加容量函数
int Check_Capacity(Contact* pc)
{
	if (pc->sz == pc->capacity) //通讯录如果放满
	{
		PeoInfo* tmp = NULL;
		tmp = (PeoInfo*)realloc(pc->data, sizeof(PeoInfo) * (pc->capacity + ADD_CAPACITY));
		if (tmp == NULL)  //增容失败
		{
			perror("Check_Capacity");
			return 0;
		}
		//增容成功
		pc->data = tmp;
		pc->capacity += ADD_CAPACITY;
		printf("增容成功!\n");
		return 1;
	}
	//通讯录没放满
	return 1;
}
//增加联系人   ------   动态版本
void AddContact(Contact* pc)
{
	assert(pc);//我们不希望传入的pc指针为空,使用assert,使代码健壮性更强
	//判断通讯录是否放满,是否需要增容
	int ret = Check_Capacity(pc);

	if (0 == ret)//增容失败
		return;
	//增容成功
	printf("请输入姓名:");
	scanf("%s", pc->data[pc->sz].name);//name是一个数组名,表示地址,所以不需要取地址
	printf("请输入年龄:");
	scanf("%d", &(pc->data[pc->sz].age));//age为int类型,需要取地址
	printf("请输入性别:");
	scanf("%s", pc->data[pc->sz].sex);
	printf("请输入电话:");
	scanf("%s", pc->data[pc->sz].tel);
	printf("请输入地址:");
	scanf("%s", pc->data[pc->sz].addr);
	printf("添加联系人成功!\n");
	pc->sz++;
}

代码讲解

  1. 为什么使用 Check_Capacity 函数,如果将 Check_Capacity 函数内部的代码放在 AddContact 函数内部也是可以的,但是我们这样做增加了 AddContact 函数代码的简洁性
  2. realloc函数内部的参数解释:
    第一个参数:是要调整的空间,也就是data指向的空间
    第二个参数:pc->capacity是原来通讯录的容量,realloc函数的参数是我们需要将原空间调整后的大小,这个大小也就是原空间加上新增的空间大小。
  3. 最后在给pc->capacity赋值,原有空间大小的基础上,加上新增大小
  4. 不懂为什么使用tmp的,以及对realloc的使用不了解的,可以看一下往期讲动态内存管理的文章
#define ADD_CAPACITY 3		//默认每次增加的容量
PeoInfo* tmp = NULL;
tmp = (PeoInfo*)realloc(pc->data, sizeof(PeoInfo) * (pc->capacity + ADD_CAPACITY));
if (tmp == NULL)  //增容失败
{
	perror("Check_Capacity");
	return 0;
}
//增容成功
pc->data = tmp;
pc->capacity += ADD_CAPACITY;//原有空间大小的基础上,加上新增大小

4. 销毁通讯录

因为我们存放联系人的空间是动态开辟的,所以在使用完这块空间,也就是退出通讯录之前,我们需要对空间进行释放,我们定义一个DestroyContact函数,用来释放空间
在这里插入图片描述
contact.h文件:

//释放通讯录
DestroyContact(Contact* pc);//函数的声明

contact.c文件:

DestroyContact(Contact* pc)
{
	free(pc->data);
	pc->data = NULL;//指针置空
	pc->capacity = 0;//容量清空
	pc->sz = 0;//联系人个数清空
}

至此,我们就解决了通讯录的第一个问题,通讯录的大小不再被固定写死,可以动态调整。

二、第二个问题

想解决第二个问题,我们将用到fopen函数

1. 将数据写入到文件中

————————未完结

  • 16
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值