address list(dynamic)- 通讯录动态版本

address list(dynamic)- 通讯录动态版本

经过上篇文章,我们学习到了 动态内存分配,最近看过我博客的人,应该记得 我写过 一篇 关于 address list 的文章今天我们就来用新学的知识来改进它,



在这里插入图片描述

为了方便理解我 用代码注释来讲讲我们该怎么修改上方截图的程序


修改一(宏定义 与 结构体成员):

//#define max 1000  // max 使我们一开始就规定好了的 可以存 1000 个 人的信息
                  // 如果 我们想存10个人,那是不是浪费了 可以存储 990 个人 的信息 空间
                  // 有如果,我想存 1001 个人信息,那多出的那一个人,我就无法存入了
                  // 所以 这条语句 我们不需要(删除)
                  
#define SZ 3  // 默认初识容量为 3,被使用的地方,在修改 2 ,容量(capacity)初始化赋值

#define max_name 20
#define max_sex 5
#define max_tele 12
#define max_addr 30  // 方便修改 参数

enum option
{
	Exit,// 0
	Add,// 1
	Del,// 2
	Search,// 3
	Modification,//4
	Show,// 5
	Sort// 6
};


struct people
{
	char name[max_name];
	int age;
	char sex[max_sex];
	char telephone[max_tele];
	char address[max_addr];
};


// 通讯录类型
struct contact
{
	// 这里实现的是一个结构体嵌套

	//struct people data[max];// 那么这里就不能这么写了。


	 struct people* data;    // 有的人可能会说用柔性数组,柔性数组 比 替代方法
	                        // 但是 我们这 需要malloc开辟一次空间,不涉及多次开辟
	                        // 所以这里我们是使用 替代方法
	                        // 创建一个指针 来接收 我们 开辟的动态空间地址
	                        
	int size;// 记录当前已经有的元素个数
};



修改二(初始化修改):

在这里插入图片描述

原来的初始化:

在这里插入图片描述

改进初始化:

在这里插入图片描述

好的,让我们来通过程序注释,来解释一下为什么要这么该

void initcontact(struct contact* ps)
{
	assert(ps);// 判断指针 是否 空指针,原先的忘记加了 == ,勤快的人可以自己加上
	ps->data = (struct people*)malloc(SZ , (sizeof(struct people))); // malloc 开辟一个动态空间,并将其地址赋给 结构体指针变量 data。
	if (ps->data == NULL)// 判断  malloc 函数 开闭动态空间 是否成功
	{
		return;// 不成功,我们什么也做不了(啥事都不干)
	}
	else
	{// 开辟成功
		ps->size = 0;// size 初始化;     size: 记录当前存储了 几个元素
		ps->capacity = SZ;  // 初始化 容量 为 3
	}
}



修改三(函数功能修改):

int main()
{
	int input = 0;

	// 创建一个通讯录
	struct  contact contact;// contact就是通讯录,里面 包含 data 指针, size,capacity

	//初识化 通讯录
	initcontact(&contact);// 把 结构体数组 初始化为 0,只有传址,才能改变 contact 的值
	//

	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case Add:
		
			add_contact(&contact);// 增加一个通讯录信息函数
			                      // 涉及容量更改,实现函数的定义,需做修改
			break;
		case Del:
			del_contact(&contact);//不涉及 容量 更改,不用修改
			break;
		case Search:
			search_contact(&contact);//不涉及 容量 更改,不用修改
			break;
		case Modification:
			modification_contact(&contact);//不涉及 容量 更改,不用修改
			break;
		case Show:
			show_contact(&contact);//不涉及 容量 更改,不用修改
			break;
		case Sort:
			sort_contact(&contact);//不涉及 容量 更改,不用修改
			break;
		case Exit:
		
			Destroy_Contact(&contact);// 开辟了 动态 空间,就会有释放空间
			                           	// 所以在退出通讯录之前,销毁通讯录(释放动态开辟的空间)
			                           	// 故 我们 要在这里加一个 销毁通讯录 的功能
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}


修改三(函数功能修改)- add:

原 add 函数实现:

在这里插入图片描述

修改后的 add:

void CheckCapacity(struct contact* ps)
{
	if (ps->capacity == ps->size)// 存储数据的个数 等于 容量,说明容量满了
	{
		// 增容: 使用 realloc 函数 “扩展空间”
		struct people* ptr = (struct people *)realloc(ps->data, (ps->capacity + 2)*sizeof(struct people));
		if (!ptr)// 判断  realloc  "扩展空间"  是否成工本费
		// ptr == NULL == 0 (开辟失败)为假, !(取反) 变为 1 ,即为真,执行 if 语句
		{
			printf("增容失败\n");
		}
		else
		{  //开辟成功
			ps->data = ptr;// 将 realloc 开辟成功 动态空间的地址赋给  结构体指针比阿娘 data
			ps->capacity += 2;// 容量加2
			printf("增容成功\n");
		}
	}
}

void add_contact(struct contact* ps)
{
	assert(ps);
	CheckCapacity(ps);// 检测当前通讯录的容量;1. 如果满了,就增加空间, 2.如果不满,啥事都不干

	// 扩容成功就意味着 -> 现在可以增加数据
	printf("请输入名字:");
	scanf("%s", ps->data[ps->size].name);
	printf("请输入年龄:");
	scanf("%d", &(ps->data[ps->size].age));
	printf("请输入性别:");
	scanf("%s", ps->data[ps->size].sex);
	printf("请输入电话:");
	scanf("%s", ps->data[ps->size].telephone);
	printf("请输入地址:");
	scanf("%s", ps->data[ps->size].address);
	ps->size++; // 存入一个, size 由 0 变为 1
	printf("添加成功\n");
有的人可能会有疑问,如果 调用函数 CheckCapacity(ps); 玩 之后,后面又没有限制条件, 后面的 增加数据的程序 还是会 运行,因此会在想:要不要加个限制条件,或者把它 放进 函数 CheckCapacity(ps) 开辟动态空间成功 语句下。‘’

其实不用,如果 realloc 函数 追加动态空间失败。返回旧地址, 而后面增加数据 部分的 size 在上一次执行完程序后,自增加一,如果此时进行解引用访问 【 ps->data[ps->size].name 】,去赋值。会导致程序 崩溃,就更别提赋值了,

这种错误,看过我 动态分配分配 的 文章的人,此时就应该明白,这是属于 动态内存分配 常见常见错误之一 : 对动态开辟的内存 的 越界访问,

有疑问的朋友,可以返回去看看,重温一篇 动态内存分配 常见的 6个错误,在这里我只提一下,不作解释
1、 对 空指针 的解引用(开辟动态空间失败,返回一个空指针,对其解引用)
2. 对 动态开辟的内存 的 越界访问
3. 重复 释放 同一块 动态开辟的内存
4. 释放 动态开辟的空间 的 一部分
5. 对 非动态 开辟的内存,使用 free
6. 忘记 释放 开辟的动态空间内存,导致 内存泄露 问题

}

修改四(排序功能):

原排序程序:
在这里插入图片描述

修改后:
在这里插入图片描述

因为 data 不再是数据,而是一个指针,所以不能再使用原来的方法了,在这们使用 memcpy 来进行数据拷贝,以前的赋值交换,现在拷贝交换。

创建一个相同类型的结构体,用作 中转 数据。其实仔细一看,该方法就是冒泡排序的暴力求解法。以前是赋值给一个变量,利用它来完成 数据 的 移动。只不过这里编程拷贝了。另外,请注意 加上 & 符号,因为 data 已经不是一个数组了,所以需要加上该符号。这里 memcpy 的 三条语句 意思就是 把两个地址的空间里内容进行 交换。通过 临时创建结构体 tmp。

至于 if 语句 就很懂了,就是把名字(字符串)进行比较,如果 前者 比 后者 大,strcmp 就会返回一个大于0的数,就以为 前者需要 跟 后者 交换位置(前者 前进一位),后面我就不细说(冒泡),琢磨一下大家都应该懂,不懂的,可以在下方评论,我可以讲解一下(不过不能保证你一定看得懂了)。

修改五(函数功能修改)- 在 功能 Exit 中 添加一个功能(销毁通讯录):

在这里插入图片描述

这个相信大家都懂就是 释放 开辟的动态空间,因为我们只开辟一次动态空间,并将其地址赋给 data,其后 realloc 函数 追加空间后的地址也赋予了 data【realloc函数在原空间不足够大的时候,会重新找一块内存,开辟一块新的空间,并将原空间的拷贝下来,放进新的空间,并原空间释放。如果原空间足够大,则直接追加,返回旧地址,所以不用担心会存在 内存泄露 问题】。

所以我们 只用 释放 释放 data 所指向空间 就可以了。 至此 一个 通讯录(动态版本)address list (dynamic) 就完成



下面附上 修改后三张总图,本文章到此就结束了。

test.c

在这里插入图片描述

address_list.h

在这里插入图片描述

address_list.c

在这里插入图片描述

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dark And Grey

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值