简易版通讯录存在的问题
再过去的文章里,我们讲过了简易版通讯录。
简易版通讯录中,存在两个问题:
- 存放联系人信息的data数组中,存放的数据个数是被固定写死的,如果将来通讯录放满了,就放不下新的联系人了。
- 每次退出程序,数据并不会保存,这样无法达到我们保存联系人的目的。
接下来,我来带着大家解决上述提到的两个问题。
一、第一个问题
1. 改造通讯录结构体
之前我们是这样定义通讯录结构体的,data数组被写死,那我们该如何解决呢?
- 改造前的通讯录结构体:
typedef struct Contact //定义通讯录的结构体
{
PeoInfo data[100];
int sz;
}Contact;
往期我们还讲过,如果需要动态申请内存空间,就需要使用动态内存管理函数。
动态申请的空间需要指针来维护,所以我们把原来的data数组,改成PeoInfo* 类型的data指针,这样待会就可以使用malloc函数动态申请空间,用data指针来维护,并且当通讯录需要扩容的时候,我们可以使用realloc函数调整malloc动态申请的空间的大小。
- 改造后的通讯录结构体:
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)
}
- malloc函数开辟的空间大小的解释:
我们开辟的空间未来要存放联系人的信息,类型为PeoInfo类型,所以开辟CAPACITY个PeoInfo类型的空间大小。 - #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++;
}
代码讲解
- 为什么使用 Check_Capacity 函数,如果将 Check_Capacity 函数内部的代码放在 AddContact 函数内部也是可以的,但是我们这样做增加了 AddContact 函数代码的简洁性
- realloc函数内部的参数解释:
第一个参数:是要调整的空间,也就是data指向的空间
第二个参数:pc->capacity是原来通讯录的容量,realloc函数的参数是我们需要将原空间调整后的大小,这个大小也就是原空间加上新增的空间大小。 - 最后在给pc->capacity赋值,原有空间大小的基础上,加上新增大小
- 不懂为什么使用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. 将数据写入到文件中
————————未完结