文章目录
1. 前言
之前向大家介绍了C语言实现通讯录管理系统1.0版本,但该版本有明显的不足之处,比如:一开始就开辟了1000个date数组,如果联系人很少,那么就会造成严重的内存浪费,或者联系人超过了1000人,那么原数组就放不下了,所以今天我们考虑使用动态内存管理的办法来实现一个内存会随着联系人的增加而增加的通讯录动态增长版!
首先我们先来学习如何进行动态内存分配及动态内存函数的使用方法。
2. 动态内存函数
2.1 malloc函数
定义:void* malloc(size_t size);
功能:向内存申请一块连续可用的空间,并返回指向这块空间的指针。
注意事项:
size的单位是字节
如果开辟成功,则返回一个指向开辟好空间的指针。
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
返回值的类型是void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定,使用前强制类型转换。
如果参数size为0,malloc的行为是标准是未定义的,取决于编译器。
使用方法:
int main()
{
//申请空间
int* ptr = (int*)malloc(40);
int* p = ptr;
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*p = i;
p++;
}
//释放空间
free(ptr);
ptr = NULL;
return 0;
}
2.2 free函数
定义:void free(void* str);
功能:free函数用来释放动态开辟的内存。
注意事项:
如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数ptr是NULL指针,则函数什么事都不做。
当我们不释放动态申请的内存的时候:
如果程序结束,动态申请的内存由操作系统自动回收。
但是如果程序不结束,动态内存是不会自动回收的,就会形成内存泄露的问题。
所以只要动态开辟了内存,使用完就需要用free函数释放。
2.3 calloc函数
定义:void* calloc(size_t num, size_t size);
功能:为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0。
注意事项:
与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为全0。
使用方法:
int main()
{
//申请空间
int* ptr = (int*)calloc(10, sizeof(int));
int* p = ptr;
if (p == NULL)
{
perror("calloc");
}
//使用空间
//释放空间
free(ptr);
ptr = NULL;
return 0;
}
2.4 realloc函数
定义:void* realloc(void* ptr, size_t size);
功能:realloc函数可以做到对动态开辟内存大小的调整。
注意事项:
ptr是要调整的内存地址。
size调整之后新大小。
返回值为调整之后的内存起始位置。
这个函数在调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有足够大的空间
扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况2:原有空间之后没有足够大的空间
扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。
此时就要注意,如果整个堆区都没有一个合适大小的连续空间,就会申请失败。
所以使用完realloc函数应该先判断返回的地址是否为空,不为空就可以正常使用了。
使用方法:
int main()
{
//申请空间
int* ptr = (int*)malloc(40);
if (ptr == NULL)
{
perror("malloc");
return 1;
}
//使用空间
int* p = ptr;
int i = 0;
for (i = 0; i < 10; i++)
{
*p = i;
p++;
}
//扩容空间
int* p2 = (int*)realloc(ptr, 80);
if (p2 == NULL)
{
perror("realloc");
}
p = p2;
//使用空间
//释放空间
free(p);
p = NULL;
return 0;
}
3. 优化通讯录程序
学习完这些动态内存函数以后,我们就可以用动态内存分配来优化通讯录代码,避免内存浪费和内存不足的问题。
3.1 通讯录的优化
我们原来创建的通讯录是直接创建了1000个元素,而动态内存版本我们首先应该考虑如何修改通讯录,来满足我们的需求,这里我有两个想法:
1.通讯录初始只能存放3个联系人的信息。
2.当通讯录满的时候,每次增加2个人的空间。
这样修改的话,既避免了内存的浪费,也不会出现内存不够的情况。
具体修改方式如下:
//静态版本
//typedef struct contact
//{
// people date[MAX];//1000人的信息
// int count;//已保存的信息个数
//}contact;
//动态版本
typedef struct contact
{
people* date;//1000人的信息
int count;//已保存的信息个数
int cap;//记录当前通讯录的最大容量
}contact;
3.2 初始化通讯录的优化
接下来就该初始化了,初始化通讯录的时候用malloc函数来开辟内存。
//静态版本
//void InitContact(contact* pc)
//{
// assert(pc);
// pc->count = 0;
// memset(pc->date, 0, sizeof(pc->date));
//}
//动态版本
void InitContact(contact* pc)
{
assert(pc);
pc->count = 0;
pc->cap = DEFAULT_SIZE;
pc->date = (people*)malloc((pc->cap) * (sizeof(people)));
if (pc->date == NULL)
{
perror("InitContact::malloc");
return;
}
memset(pc->date, 0, (pc->cap) * (sizeof(people)));
}
当退出通讯录的时候,我们要释放动态开辟的内存,所以还应该添加一个销毁通讯录的函数。
void DestroyContact(contact* pc)
{
free(pc->date);
pc->date = NULL;
pc->cap = 0;
pc->count = 0;
printf("销毁成功\n");
}
3.3 添加联系人的优化
接下来就是添加联系人的时候加入判断函数,判断此时通讯录是否为满,如果满了就再开辟2个联系人的内存,每次添加都应该判断一次。
void CheckCapacity(contact* pc)
{
if (pc->count == pc->cap)
{
people* ptest = (people*)realloc(pc->date, ((pc->cap) + 2) * (sizeof(people)));
if (ptest != NULL)
{
pc->date = ptest;
}
else
{
perror("CheckCapacity::realloc");
return;
}
pc->cap += 2;
printf("扩容成功\n");
}
}
void AddPeople(contact* pc)
{
assert(pc);
//静态版本
//if (pc->count == MAX)
//{
// printf("通讯录已满,无法添加\n");
// return;
//}
//动态版本
CheckCapacity(pc);
printf("请输入姓名:\n");
scanf("%s", pc->date[pc->count].name);
printf("请输入性别:\n");
scanf("%s", pc->date[pc->count].sex);
printf("请输入年龄:\n");
scanf("%d", &pc->date[pc->count].age);
printf("请输入号码:\n");
scanf("%s", pc->date[pc->count].tele);
printf("请输入地址:\n");
scanf("%s", pc->date[pc->count].address);
pc->count++;
printf("添加成功\n");
}
3.4 运行测试
我们来运行代码,观察结果
可以看出,这时候代码已经优化完成,初始只能保存3个联系人的信息,当继续存储时就会扩容。如果退出程序,就会销毁(释放)掉动态内存分配的空间,不会造成内存泄漏的问题。
4. 结尾
到这里,我们动态增长版本的通讯录管理系统2.0也就优化完成了,后续我还会对代码进行优化,敬请期待。