我们之前讲解了使用结构体的知识实现通讯录管理,在我们学习过动态内存管理和文件操作后,能否对通讯录进行升级呢?今天这篇文章就会为大家讲解通讯录的升级版。
文章目录
一.简易通讯录回顾
1.通讯录功能
在之前的简易通讯录中我们通过创建结构体来存放通讯录中联系人的信息,设置变量维护联系人的个数,也实现了通讯录的增删改、显示、排序、查找、清理等多个功能。
2.通讯录缺点
虽说简易通讯录已经可以实现多种功能了,当时其逻辑和代码上还有很多需要改进升级的地方。简易通讯录还有一些缺点: 比如在我们创建结构体指定通讯录可存放联系人个数的大小时,是将数组大小写死的,这就导致代码使用起来灵活度较低,如果数组大小开辟的不合适,很容易出现空间不够非法访问或者内存浪费等问题。这个问题我们可以使用动态内存来解决,提高代码的灵活度。
另外一个缺点就是,通讯录中的联系人信息无法持久保存,在终端关闭后存放的信息就会被销毁,下一次运行程序时只能重新录入信息存,这就导致简易通讯录的实用性大大降低 ,这里我们就可以使用文件操作的知识,对联系人的信息进行储存维护。理论存在,实践开始:
二.动态内存升级通讯录
为了避免把存放联系人信息的个数给写死,显然我们不可继续使用数组维护结构体联系人的信息。
我们选择重新创建一个结构体 :创建一个结构体指针指向存放联系人信息的结构体,创建一个整形计算已存联系人的个数,再创建一个变量计算当前通讯录的容量,这是实现动态开辟存放联系人个数的重要变量。
如下:
typedef struct peoinf//存放个人的信息
{
char name[20];
int age;
char sex[6];
char addr[15];
char tele[13];
}peo;
typedef struct Contacts//通讯录包括联系人信息和联系人个数
{
peo* data;
int sz;
int Capacity;//容量
}contacts;
1.动态初始化通讯录
在先前的版本中,初始化通讯录只需将数组和联系人个数置为零即可,而在动态版本中我们需要将为指针开默认初始容量的内存空间,再将联系人个数置零,容量置为默认初始容量。
如下:
#define Initial_value 3//默认容量
#define Step 2//增容步长
//动态
void ini_con(contacts* p)
{
assert(p);
p->sz = 0;
p->Capacity = Initial_value;//容量置为默认
peo* ptr=(peo*)calloc(Initial_value, sizeof(peo));
if (NULL == ptr)//开辟默认容量的空间,并且初始化为零
{
perror("calloc:");
return;
}
p->data = ptr;
}
2.动态规划容量
因为我们设置默认通讯录的容量仅为3,所以我们在添加联系人信息时,一定要考虑联系人个数是否要超出容量的问题,所以我们还要编写函数为通讯录增加容量,而每次增加的大小用define定义为2。使用realloc函数为通讯录增容:
#define Initial_value 3//默认容量
#define Step 2//增容步长
void Increased_capacity(contacts* p)
{
if (p->sz == p->Capacity)//判断是否需要增容
{
peo* ptr = (peo*)realloc(p->data, sizeof(peo) * (Initial_value + Step));
if (ptr == NULL)
{
perror("realloc:");
return;
}
printf("增容成功\n");
p->data = ptr;
p->Capacity += Step;//一次增容,容量加一次步长
}
}
3.释放动态内存空间
使用动态内存对通讯录升级后,在退出通讯录时应该记得释放掉动态开辟的内存防止 内存泄漏:
void Destroy(contacts * p)
{
assert(p);
free(p->data);
p->data = NULL;
p->sz = 0;
p->Capacity = 0;
p = NULL;
}
三.文件操作升级通讯录
在使用动态内存管理升级简易通讯录后,通讯录的灵活度得到了提升,减少了内存的浪费,但是这个通讯录管理还是存在着致命的问题------无法将联系人持久保存,在下次运行程时无法保留上次的联系人信息,那我们如何运用文件操作的知识解决这个问题呢?如下:
1.保存通讯录
我希望这次运行的通讯录中保存的信息,在结束程序后依然存在,我们就可以创建一个文件,在退出通讯录时将联系人的信息写入文件,在下次运行程序时再向文件中读取。这里需要用到两个函数fwrite和fread:
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
通过观察函数原型可知,fwrite可将ptr指向地址的count个size的字节写入stream指向的文件信息区。
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
fread与rwrite相似,只不过它是把stream指向的内容存放到ptr指向的空间。
这样我们就可以去实现保存通讯录的函数了:
//保存通讯录
void Savecon(contacts* p)
{
assert(p);
FILE* pf = fopen("contacts.txt", "wb");
if (NULL == pf)
{
perror("fopen:");
}
else
{
int i = 0;
for (i = 0; i < p->sz; i++)
{
fwrite(p->data + i, sizeof(peo), 1, pf);
}
fclose(pf);//记得关闭文件
pf = NULL;
printf("保存成功\n");
}
}
2.加载通讯录
我们在上次运行通讯录程序结束后,会将联系人信息保存到文件中,而为了下次运行程序依旧保存着上次的信息,我们在初始化通讯录后,就要加载上次通讯录留下的信息。
//加载通讯录
void Loadcon(contacts* p)
{
assert(p);
FILE* pf = fopen("contacts.txt", "rb");
if (NULL == pf)
{
perror("fopen:contacts.txt:");
}
else
{
peo tmp = {
0 };
int i = 0;
while (fread(&tmp, sizeof(peo), 1, pf))
{
Increased_capacity(p);//判断是否需要增容
p->data[i] = tmp;
i++;
p->sz++;
}
fclose(pf);
pf = NULL;
}
}
需要注意的是,读取上次通讯录留下的信息时,应判断默认容量是否能够放的下,如果不能就要进行增容!