动态通讯录的实现
什么是动态通讯录
在设计第一版本的简单通讯录的时候,我们留下过一个问题,就是通讯录的容量是固定的,有可能会出现不够用或者浪费空间过多的问题,为了解决这一问题,我们就希望通讯录的大小能够随着联系人的多少动态变化,这就是动态通讯录。
由于动态通讯录的升级只需要修改简单通讯录的部分操作,因此重复的部分在本篇博客中就不再细讲,大家具体可以观看上一篇博客。
博客链接:
简单通讯录实现(C语言)—在添加联系人的时候完成了排序工作,并且用二分查找方式寻找联系人
前言
由于作者在设计动态通讯录的时候使用了柔性数组的知识,所以如果有对柔性数组知识不熟悉的小伙伴需要现看一下下面这篇博客学习一下柔性数组:
接下来,就开始实现动态通讯录吧!哇酷哇酷!
实现逻辑
1.新的结构体定义
首先,由于通讯录的容量是动态的,所以我们对通讯录结构体的定义就需要做出一点改变,除了需要标识出当前通讯录内的联系人数量,还需要知道当前通讯录内的最大容量,并且由于要使用柔性数组,因此原来通讯录的数组成员也要改变。
由此就得出了新的结构体定义:
typedef struct contact
{
int sz;
int max_ele;
per_info people[];
}contact;
由于用的是柔性数组的方式,所以在创建通讯录时就不能直接用该结构体了,而是创建一个contact
结构体的指针,通过动态内存分配操作他指向的空间。
contact* record = NULL;
//创建通讯录,置为NULL避免误用野指针
注意
由于在使用通讯录时创建的是指针,而
malloc
,realloc
函数有可能会改变该空间的地址,所以在有用到malloc
,realloc
函数的功能函数,其函数参数应该为一个二级指针,并且函数实参应该给通讯录指针的地址。
//一些需要用到malloc或者realloc的函数声明:
//初始化通讯录
void init_contact(contact** pc);
//添加联系人
void add_person(contact** pc);
//删除联系人
void del_person(contact** pc);
2.初始化通讯录
上次在设计简单通讯录的时候,我们为了让初始化通讯录过程能够以我们的想法进行,设计了初始化通讯录函数,这就为动态通讯录的改造做下了铺垫。
试想一下,我们要初始化这一通讯录,需要做到为其分配一定量的内存,并且将通讯录里的内容全都赋值为0(除最大容量),这时就发现calloc
函数恰好可以满足这一要求。
calloc
和malloc
的区别时calloc在分配内存的同时将分配的内存里的数据全部初始化为0。
代码实现
首先,我们增加了一个宏定义便于修改参数。
#define INIT_CAPCITY 3
//初始通讯录容量
初始化函数:
void init_contact(contact** pc)
{
//pc不能是空指针
assert(pc);
contact* ret = (contact*)calloc(1, sizeof(contact) + INIT_CAPCITY * sizeof(per_info));
//为通讯录分配初始空间
if (ret)
{
//若分配成功,将地址交给通讯录,并且将通讯录的最大容量设置为初始值INIT_CAPCITY。
*pc = ret;
(*pc)->max_ele = INIT_CAPCITY;
}
else
{
//分配失败,报错并退出程序
perror("init_contact::calloc");
exit(1);
}
}
3.添加联系人(必要时增容)
我们先来理一下逻辑,首先我们的通讯录在改造之后容量是可变的,在我们添加联系人的时候,可能会遇到此时通讯录内的联系人数量相等的情况,此时我们就需要添加容量,便于下一次增加联系人。
所以相对于第一个版本,我们就多了一个检查容量的步骤,当容量不够的时候就要增加容量,我们用函数ins_capcity
来模块化处理增加容量的步骤。
3.1 增容函数
首先确定其参数类型,增容函数一定是要使用realloc
函数的,也就是说其有可能会改变通讯录的地址,所以函数参数类型也是二级指针,实现代码如下:
#define INS_ELE 2;
//每次增加的容量大小
为了使参数变化更加容易,使用宏定义
contact* ins_capcity(contact* pc)
{
contact* ret = (contact*)realloc(pc, (pc->max_ele + INS_ELE) * sizeof(per_info) + sizeof(contact));
//如果成功增容,返回增容后的地址
if (ret)
return ret;
else
//如果增容失败,则退出程序
{
perror("ins_capcity::realloc");
exit(1);
}
}
实现代码:
/*动态版本+柔性数组*/
void add_person(contact** pc)
{
assert(pc);
if ((*pc)->sz == (*pc)->max_ele)
{
*pc = ins_capcity(*pc);
//通讯录的最大容量每次也需要扩大
(*pc)->max_ele += INS_ELE;
//printf("增容成功!\n");
}
printf("请输入联系人的姓名:>");
scanf("%s", (*pc)->people[(*pc)->sz].name);
printf("请输入联系人的性别:>");
scanf("%s", (*pc)->people[(*pc)->sz].sex);
printf("请输入联系人的年龄:>");
scanf("%d", &((*pc)->people[(*pc)->sz].age));
printf("请输入联系人的地址:>");
scanf("%s", (*pc)->people[(*pc)->sz].addr);
printf("请输入联系人的电话:>");
scanf("%s", (*pc)->people[(*pc)->sz].tele);
++(*pc)->sz;
qsort((*pc)->people, (*pc)->sz, sizeof((*pc)->people[0]), cmp);
printf("添加成功!\n");
}
4.删除联系人(必要时缩容)
和增加联系人类似,如果一个通讯录在删除了大量联系人之后,也会多出了很多闲置空间,为了使容量更合理,我们当然需要减容,由于和增容类似,这里就不再梳理逻辑了,直接上代码!
void del_person(contact** pc)
{
assert(pc);
//每次在删除联系人之前判断此时的最大容量和通讯录联系人数量之间的差值是否等于INS_ELE
//如果成立,则进行缩容处理
if ((*pc)->max_ele - (*pc)->sz == INS_ELE)
{
contact* ret = (contact*)realloc(*pc, sizeof(contact) + sizeof(per_info) * ((*pc)->max_ele - INS_ELE));
if (ret)
{
*pc = ret;
(*pc)->max_ele -= 1;
//printf("缩容成功!\n");
}
else
perror("del_person::realloc");
}
printf("请输入你要删除的联系人:>");
char name[NAME_MAX] = { 0 };
scanf("%s", name);
int pos = find_by_name(*pc, name);
if (-1 == pos)
{
printf("你要删除的联系人不存在.\n");
return;
}
for (int i = pos; i < (*pc)->sz - 1; ++i)
{
(*pc)->people[i] = (*pc)->people[i + 1];
}
--(*pc)->sz;
printf("删除成功!\n");
}
5.清空联系人
清空联系人行为,我们首先需要将容量重新用动态内存分配的方式变成初始容量大小,然后用memset
的方式将结构体内的东西都置为0,最后将最大容量设为INIT_CAPCITY
即可。
void empty_person(contact** pc)
{
assert(pc);
contact* ret = realloc(*pc, INIT_CAPCITY * sizeof(per_info) + sizeof(contact));
if (ret)
{
*pc = ret;
}
else
{
perror("empry_person::realloc");
exit(1);
}
memset(*pc, 0, INIT_CAPCITY * sizeof(per_info) + sizeof(contact));
(*pc)->max_ele = INIT_CAPCITY;
printf("清空联系人成功\n");
}
6.动态内存的销毁问题
我们知道,对于动态分配的内存,我们在不使用之后都是需要释放的,否则就会出现内存泄漏的问题,所以我们可以再设立一个函数destory_contact
函数,用来销毁动态分配的内存。
void destory_contact(contact* pc)
{
pc->sz = 0;
free(pc);
pc = NULL;
}
至此,动态通讯录改造的任务就完成了,我们再来看一下代码总览。
7.代码总览
contact.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#define PEOPLE_MAX 100
#define SEX_MAX 5
#define TELE_MAX 12
#define NAME_MAX 20
#define ADDR_MAX 25
#define INIT_CAPCITY 3
#define INS_ELE 2
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
#include<windows.h>
typedef enum option
{
EXIT,
ADD,
DEL,
MODIFY,
SEARCH,
SHOW,
EMPTY
}option;
typedef struct per_info
{
int age;
char sex[SEX_MAX];
char tele[TELE_MAX];
char name[NAME_MAX];
char addr[ADDR_MAX];
}per_info;
/*静态版本*/
//typedef struct contact
//{
// per_info people[PEOPLE_MAX];
// int sz;
//}contact;
/*动态版本*/
typedef struct contact
{
int sz;
int max_ele;
per_info people[];
}contact;
//使用传址调用能够减少空间的使用
//初始化通讯录
void init_contact(contact** pc);
//添加联系人
void add_person(contact** pc);
//显示联系人
void show_person(const contact* pc);
//搜索联系人
void search_person(const contact*pc);
//删除联系人
void del_person(contact** pc);
//修改联系人
void mod_person(contact* pc);
//清空联系人
void empty_person(contact** pc);
//销毁通讯录
void destory_contact(contact* pc);
contact.c
#include"contact.h"
/*静态版本*/
//void init_contact(contact* pc)
//{
// //pc不能是空指针
// assert(pc);
// memset(pc->people, 0, sizeof(pc->people));
// pc->sz = 0;
//}
/*动态版本+柔性数组*/
void init_contact(contact** pc)
{
//pc不能是空指针
assert(pc);
contact* ret = (contact*)calloc(1, sizeof(contact) + INIT_CAPCITY * sizeof(per_info));
if (ret)
{
*pc = ret;
(*pc)->max_ele = INIT_CAPCITY;
}
else
{
perror("init_contact::calloc");
}
}
int cmp(const void* a, const void* b)
{
return strcmp(((per_info*)a)->name, ((per_info*)b)->name);
}
/*静态版本*/
//void add_person(contact* pc)
//{
// assert(pc);
// printf("请输入联系人的姓名:>");
// scanf("%s", pc->people[pc->sz].name);
// printf("请输入联系人的性别:>");
// scanf("%s", pc->people[pc->sz].sex);
// printf("请输入联系人的年龄:>");
// scanf("%d", &(pc->people[pc->sz].age));
// printf("请输入联系人的地址:>");
// scanf("%s", pc->people[pc->sz].addr);
// printf("请输入联系人的电话:>");
// scanf("%s", pc->people[pc->sz].tele);
// ++pc->sz;
// qsort(pc->people, pc->sz, sizeof(pc->people[0]), cmp);
// printf("添加成功!\n");
// Sleep(1000);
// system("cls");
//}
contact* ins_capcity(contact* pc)
{
contact* ret = (contact*)realloc(pc, (pc->max_ele + INS_ELE) * sizeof(per_info) + sizeof(contact));
if (ret)
return ret;
else
perror("ins_capcity::realloc");
}
/*动态版本+柔性数组*/
void add_person(contact** pc)
{
assert(pc);
if ((*pc)->sz == (*pc)->max_ele)
{
*pc = ins_capcity(*pc);
(*pc)->max_ele += INS_ELE;
//printf("增容成功!\n");
}
printf("请输入联系人的姓名:>");
scanf("%s", (*pc)->people[(*pc)->sz].name);
printf("请输入联系人的性别:>");
scanf("%s", (*pc)->people[(*pc)->sz].sex);
printf("请输入联系人的年龄:>");
scanf("%d", &((*pc)->people[(*pc)->sz].age));
printf("请输入联系人的地址:>");
scanf("%s", (*pc)->people[(*pc)->sz].addr);
printf("请输入联系人的电话:>");
scanf("%s", (*pc)->people[(*pc)->sz].tele);
++(*pc)->sz;
qsort((*pc)->people, (*pc)->sz, sizeof((*pc)->people[0]), cmp);
printf("添加成功!\n");
Sleep(1000);
system("cls");
}
void show_person(const contact* pc)
{
system("cls");
assert(pc);
printf("%-20s\t%-4s\t%-5s\t%-12s\t%-25s\n", "姓名", "年龄", "性别", "电话", "地址");
for (int i = 0; i < pc->sz; ++i)
{
printf("%-20s\t%-4d\t%-5s\t%-12s\t%-25s\n", pc->people[i].name,
pc->people[i].age,
pc->people[i].sex,
pc->people[i].tele,
pc->people[i].addr);
}
printf("\n\n");
}
int find_by_name(const contact* pc, char* name)
{
int left = 0;
int right = pc->sz - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (strcmp(pc->people[mid].name, name) > 0)
right = mid - 1;
else if (strcmp(pc->people[mid].name, name) < 0)
left = mid + 1;
else
return mid;
}
return -1;
}
void search_person(const contact* pc)
{
assert(pc);
char name[NAME_MAX] = { 0 };
printf("请输入你要查找联系人的姓名:>");
scanf("%s", name);
int pos = find_by_name(pc, name);
if (-1 == pos)
{
printf("找不到该联系人\n");
return;
}
printf("%-20s\t%-4s\t%-5s\t%-12s\t%-25s\n", "姓名", "年龄", "性别", "电话", "地址");
printf("%-20s\t%-4d\t%-5s\t%-12s\t%-25s\n", pc->people[pos].name,
pc->people[pos].age,
pc->people[pos].sex,
pc->people[pos].tele,
pc->people[pos].addr);
}
void del_person(contact** pc)
{
assert(pc);
if ((*pc)->max_ele - (*pc)->sz == INS_ELE)
{
contact* ret = (contact*)realloc(*pc, sizeof(contact) + sizeof(per_info) * ((*pc)->max_ele - INS_ELE));
if (ret)
{
*pc = ret;
(*pc)->max_ele -= 1;
//printf("缩容成功!\n");
}
else
perror("del_person::realloc");
}
printf("请输入你要删除的联系人:>");
char name[NAME_MAX] = { 0 };
scanf("%s", name);
int pos = find_by_name(*pc, name);
if (-1 == pos)
{
printf("你要删除的联系人不存在.\n");
return;
}
for (int i = pos; i < (*pc)->sz - 1; ++i)
{
(*pc)->people[i] = (*pc)->people[i + 1];
}
--(*pc)->sz;
printf("删除成功!\n");
Sleep(1000);
system("cls");
}
void mod_person(contact* pc)
{
printf("请输入你要修改的联系人的名字:>");
char name[NAME_MAX] = { 0 };
scanf("%s", name);
int pos = find_by_name(pc, name);
if (-1 == pos)
{
printf("找不到要修改的联系人。\n");
return;
}
printf("请输入联系人的姓名:>");
scanf("%s", pc->people[pos].name);
printf("请输入联系人的性别:>");
scanf("%s", pc->people[pos].sex);
printf("请输入联系人的年龄:>");
scanf("%d", &(pc->people[pos].age));
printf("请输入联系人的地址:>");
scanf("%s", pc->people[pos].addr);
printf("请输入联系人的电话:>");
scanf("%s", pc->people[pos].tele);
qsort(pc->people, pc->sz, sizeof(pc->people[0]), cmp);
printf("修改成功!\n");
Sleep(1000);
system("cls");
}
/*静态版本*/
//void empty_person(contact* pc)
//{
// assert(pc);
// if (!pc->sz)
// {
// printf("无联系人,无需清空。\n");
// Sleep(1000);
// system("cls");
// return;
// }
// memset(pc, 0, sizeof(contact));
// printf("清空联系人成功!\n");
// Sleep(1000);
// system("cls");
//}
/*动态版本*/
void empty_person(contact** pc)
{
assert(pc);
contact* ret = realloc(*pc, INIT_CAPCITY * sizeof(per_info) + sizeof(contact));
if (ret)
{
*pc = ret;
}
else
perror("empry_person::realloc");
memset(*pc, 0, INIT_CAPCITY * sizeof(per_info) + sizeof(contact));
(*pc)->max_ele = INIT_CAPCITY;
printf("清空联系人成功\n");
Sleep(1000);
system("cls");
}
void destory_contact(contact* pc)
{
pc->sz = 0;
free(pc);
pc = NULL;
}
text.c
#include"contact.h"
void menu(void)
{
printf("****************************\n");
printf("**** 1.add 2.del ****\n");
printf("**** 3.modify 4.search ****\n");
printf("**** 5.show 6.empty ****\n");
printf("**** 0.exit ****\n");
printf("****************************\n");
}
int main()
{
int input = 0;
//创建通讯录
contact* record ;
//为使得初始化更灵活,用函数封装
init_contact(&record);
do
{
menu();
printf("请选择你需要做的操作:>");
scanf("%d", &input);
while (getchar() != '\n')
continue;
system("cls");
switch (input)
{
case ADD:
add_person(&record);
break;
case DEL:
del_person(&record);
break;
case MODIFY:
mod_person(record);
break;
case SEARCH:
search_person(record);
break;
case SHOW:
show_person(record);
break;
case EMPTY:
empty_person(&record);
break;
case 0:
destory_contact(record);
printf("退出通讯录");
break;
default:
printf("输入错误,请重新输入。");
break;
}
} while (input);
return 0;
}
8.反思
动态通讯录虽然能够保证内存空间的利用更有效,但是在关闭程序后里面存储的联系人还是没能存下来,所以还需要进行改进,这将在下一篇博客处理这一问题!
对于动态通讯录,大家如果有不懂的,欢迎评论区留言哦!博主都会一一解答的嘿嘿。