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