通讯录有新的更新啦,请注意查收~~~
在静态版本的时候,我们自定义的联系人的数量为1000,这个数据大小对于内存空间来说是有点不适合的,这回我们使用动态的模式对静态版本的通讯录进行改进。
由于本次改进是基于静态版本,所以大部分的代码内容是相同的,本文仅展示修改的地方,文件代码以及文件的gitee链接仍将放置于文末,请放心食用~
目录
一、通讯录相关
1.通讯录的结构体修改
在静态版通讯录中我们会看到一个警告,内容如下:
C6262的具体信息如下:
我们设定通讯录的联系人数量初始为3,当存储到3个人信息的时候,再开辟可以存放两个联系人信息的空间添加到联系人列表后,sz仍旧用来表示当前通讯录中现有的联系人数量
首先需要创建指向存放联系人信息的指针,使用结构体指针;
除现有联系人数量sz外,还需要使用变量capacity来表示当前通讯录的最大容量:
// contact.h
typedef struct Contact
{
PeoInfo* data; // 指向动态申请的空间,用来存放联系人的信息
int sz; // 通讯录中具体的联系人数量
int capacity; // 记录当前通讯录的最大容量
}Contact;
2.通讯录的初始化
需要初始化的内容有:
(1)联系人列表(data)
首先需要给联系人列表申请一块连续的空间,这块空间位于堆上。
堆区是一个比较大的内存容器,比栈大,但是堆区内存的使用需要我们进行手动的申请和释放,在使用堆区的时候,需要引用头文件<stdlib.h>,我们将引用放在contact.h中。
需要先定义初始默认的大小DEFAULT_SZ,定义为我们设置的3,同时定义每次增加两个联系人信息的空间
// contact.h
#define DEFAULT_SZ 3
#define INC_SZ 2
我们使用malloc函数开辟空间,将开辟的空间强制类型转换为PeoInfo类型,开辟大小为默认大小与单个联系人所占空间大小相乘所占的字节数。
由于各种因素,可能会导致空间开辟失败,即开辟后联系人列表为空,若为空就是用peeror对错误信息进行输出。
(2)联系人数量(sz)
仍旧是将现有联系人数量初始化为0
(3)当前最大容量(capacity)
由于我们设置初始容量为3个联系人的空间大小,故将cappacity初始化为3
函数如下:
// contact.c
void InitContact(Contact* pc)
{
pc->data = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
if (pc->data == NULL)
{
perror("InitContact");
return;
}
pc->sz = 0;
pc->capacity = DEFAULT_SZ;
}
二、菜单中功能的修改
我们可以发现,打印,查找,排序等功能是与内存空间的改变无关的,只有增加和退出时会对内存空间的大小有影响。所以本文只修改对内存有影响的函数内容
1.增加联系人
首先要对实际存储的联系人数量与当前空间容量进行对比,当联系人数量与通讯录容量相等时,我们就需要对现有的空间进行调整。
创建CheckCapacity函数判断是否需要扩容,并将判断结果返回值增加联系人函数中。
使用realloc对空间进行调整:起始位置为联系人列表的起始地址,调整后的空间为当前最大容量加上每次增加的容量所占用空间的字节数。并强制转换为联系人列表类型,之后与初始化时进行判断,是否开辟成功。不成功就提示并退出函数。
若空间调整成功,提示增容成功,通讯录的最大容量也要随之改变。
调整之后,添加信息的代码内容与静态版本一致,不再赘述。
函数代码如下:
// contact.c
void CheckCapacity(Contact* pc)
{
if (pc->sz == pc->capacity)
{
PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));
if (ptr != NULL)
{
pc->data = ptr;
pc->capacity += INC_SZ;
printf("增容成功\n");
}
else
{
perror("AddContact");
printf("增加联系人失败\n");
return;
}
}
}
void AddContact(Contact* pc)
{
CheckCapacity(pc);
printf("请输入姓名:>");
scanf("%s", pc->data[pc->sz].name);
printf("请输入性别:>");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入年龄:>");
scanf("%d", &pc->data[pc->sz].age);
printf("请输入电话:>");
scanf("%s", pc->data[pc->sz].tel);
printf("请输入地址:>");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;
printf("增加成功\n");
}
2.销毁通讯录
由于动态开辟内存空间使用的是堆区,在程序退出时,需要对开辟的空间进行销毁,我们创建函数DestorContact对通讯录进行销毁,函数的声明和使用在静态版本时已有包含,在此不再演示。
使用free对空间进行释放;
将联系人列表置空;
将联系人数量置0;
将通讯录容量置0。
如下:
// contact.c
void DestorContact(Contact* pc)
{
free(pc->data);
pc->data = NULL;
pc->sz = 0;
pc->capacity = 0;
}
三、代码文件
1.contact.h
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:6031)
// 相关函数的声明
// 一些类型的定义
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAX_NAME 20
#define MAX_SEX 10
#define MAX_TEL 15
#define MAX_ADDR 30
#define MAX 1000
#define DEFAULT_SZ 3
#define INC_SZ 2
// 联系人的存放
// 使用typedef 定义结构体类型,方便调用
typedef struct PeoInfo
{
char name[MAX_NAME]; // 姓名
char sex[MAX_SEX]; // 性别
int age; // 年龄
char tel[MAX_TEL]; // 电话
char addr[MAX_ADDR]; // 地址
}PeoInfo;
// 通讯录
typedef struct Contact
{
PeoInfo* data; // 指向动态申请的空间,用来存放联系人的信息
int sz; // 通讯录中具体的联系人数量
int capacity; // 记录当前通讯录的最大容量
}Contact;
// 初始化通讯录
void InitContact(Contact* pc);
// 增加联系人
void AddContact(Contact* pc);
// 打印联系人
void PrintContact(const Contact* pc);
// 删除联系人
void DelContact(Contact* pc);
// 查找联系人
void SearchContact(Contact* pc);
// 修改联系人
void ModifyContact(Contact* pc);
// 排序联系人
void SortContact(Contact* pc);
// 销毁通讯录
void DestorContact(Contact* pc);
2.contact.c
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:6031)
// 具体的功能函数的实现
#include"contact.h"
// 动态版本-初始化通讯录
void InitContact(Contact* pc)
{
pc->data = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
if (pc->data == NULL)
{
perror("InitContact");
return;
}
pc->sz = 0;
pc->capacity = DEFAULT_SZ;
}
// 判断是否需要增容
void CheckCapacity(Contact* pc)
{
if (pc->sz == pc->capacity)
{
PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));
if (ptr != NULL)
{
pc->data = ptr;
pc->capacity += INC_SZ;
printf("增容成功\n");
}
else
{
perror("AddCo ntact");
printf("增加联系人失败\n");
return;
}
}
}
// 动态版本——增加联系人
void AddContact(Contact* pc)
{
// 判断空间是否足够
CheckCapacity(pc);
// 增加联系人信息
printf("请输入姓名:>");
scanf("%s", pc->data[pc->sz].name);
printf("请输入性别:>");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入年龄:>");
// 变量需要使用&
scanf("%d", &pc->data[pc->sz].age);
printf("请输入电话:>");
scanf("%s", pc->data[pc->sz].tel);
printf("请输入地址:>");
scanf("%s", pc->data[pc->sz].addr);
// 使联系人数量+1
pc->sz++;
printf("增加成功\n");
}
// 动态版本——销毁通讯录
void DestorContact(Contact* pc)
{
free(pc->data);
pc->data = NULL;
pc->sz = 0;
pc->capacity = 0;
}
// 打印联系人——加上const确保在函数内部联系人信息不会被改变
void PrintContact(const Contact* pc)
{
if (pc->sz == 0)
{
printf("通讯录中无联系人信息,无需打印\n");
return;
}
// 打印表头
printf("%-10s\t%-10s\t%-10s\t%-10s\t%-10s\n", "姓名", "性别", "年龄", "电话", "地址");
// 打印联系人信息
int i = 0;
for (i = 0; i < pc->sz; i++)
{
printf("%-10s\t%-10s\t%-10d\t%-10s\t%-10s\n",
pc->data[i].name,
pc->data[i].sex,
pc->data[i].age,
pc->data[i].tel,
pc->data[i].addr
);
}
printf("打印完成\n");
}
// 通过姓名查找联系人
// 创建查找函数,方便被删除修改和查找时调用
// 使用static 使得此函数只能在本函数内部被调用和查看
static int FindName(const Contact* pc, const char input_name[])
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
// strcmp的结果为0时是找到了对应的姓名
if (strcmp(pc->data[i].name, input_name) == 0)
{
// 则返回对应的联系人信息下标
return i;
}
}
// 找不到
return -1;
}
// 删除联系人
void DelContact(Contact* pc)
{
// 判断通讯录是否为空
if (pc->sz == 0)
{
printf("通讯录为空,无需删除\n");
return;
}
// 查找要删除的人
char input_name[MAX_NAME];
printf("请输入要删除人的姓名:>");
scanf("%s", input_name);
// 设定ret判断返回结果
int ret = FindName(pc, input_name);
// 当查找失败时
if (ret == -1)
{
printf("要删除的人不存在\n");
return;
}
// 删除后,将删除联系人之后的联系人信息的下标-1
int i = 0;
for (i = ret; i < pc->sz; i++)
{
pc->data[i] = pc->data[i + 1];
}
// 删除成功后使联系人数量减1
pc->sz--;
printf("删除成功\n");
}
// 查找联系人
void SearchContact(Contact* pc)
{
char find_name[MAX_NAME] = { 0 };
printf("请输入查找的联系人姓名:>");
scanf("%s", find_name);
int ret = FindName(pc, find_name);
if (ret == -1)
{
printf("查无此人\n");
return;
}
else
{
printf("%-10s\t%-10s\t%-10s\t%-10s\t%-10s\n", "姓名", "性别", "年龄", "电话", "地址");
printf("%-10s\t%-10s\t%-10d\t%-10s\t%-10s\n",
pc->data[ret].name,
pc->data[ret].sex,
pc->data[ret].age,
pc->data[ret].tel,
pc->data[ret].addr
);
printf("查找完毕\n");
}
}
// 修改联系人
void ModifyContact(Contact* pc)
{
char input_name[MAX_NAME] = { 0 };
printf("请输入要修改的联系人的姓名:>");
scanf("%s", input_name);
int ret = FindName(pc, input_name);
if (ret == -1)
{
printf("查无此人,无法修改\n");
return;
}
printf("请输入姓名:>");
scanf("%s", pc->data[ret].name);
printf("请输入性别:>");
scanf("%s", pc->data[ret].sex);
printf("请输入年龄:>");
// 变量需要使用&
scanf("%d", &pc->data[ret].age);
printf("请输入电话:>");
scanf("%s", pc->data[ret].tel);
printf("请输入地址:>");
scanf("%s", pc->data[ret].addr);
printf("修改完毕\n");
}
// 创建比较函数
int cmp_name(const void* p1, const void* p2)
{
return strcmp(((PeoInfo*)p1)->name, ((PeoInfo*)p2)->name);
}
// 排序联系人
void SortContact(Contact* pc)
{
// 判断通讯录是否为空
if (pc->sz == 0)
{
printf("通讯录为空,无需排序\n");
return;
}
// 以姓名为排序依据进行排序
// 使用qsort函数进行排序——调用了创建的cmp_name()
// 待排序列地址、待排关键字个数、关键字大小、比较函数地址
qsort(pc->data, pc->sz, sizeof(PeoInfo), cmp_name);
printf("排序成功\n");
}
3.test.c
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:6031)
// 测试文件
// 动态版本
// 动态增长的版本——解决初始化1000人信息时数据量过大
// 假设通讯录初始化是可存放3个联系人信息
// 空间存放满的时候,再增加存放两个联系人的空间
//
#include"contact.h"
void menu()
{
printf("*******************\n");
printf("**** 1.add ****\n"); // 增加联系人信息
printf("**** 2.del ****\n"); // 删除联系人信息
printf("**** 3.serach ****\n"); // 查找联系人信息
printf("**** 4.modify ****\n"); // 修改联系人信息
printf("**** 5.sort ****\n"); // 排序联系人信息
printf("**** 6.print ****\n"); // 打印联系人信息
printf("**** 0.exit ****\n"); // 退出通讯录
printf("*******************\n");
}
// 使用枚举,使得switch选择时,更加明了
enum Menu
{
EXIT, // 默认为 0
ADD, // 1
DEL,
SEARCH,
MODIFY,
SORT,
PRINT
};
int main() // 在静态模式下会因为数据过多报警告
{
int input = 0;
// 创建联系人列表
PeoInfo data[MAX];
// 创建通讯录
Contact contact;
// 初始化通讯录
InitContact(&contact);
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case ADD:
AddContact(&contact);
break;
case DEL:
DelContact(&contact);
break;
case SEARCH:
SearchContact(&contact);
break;
case MODIFY:
ModifyContact(&contact);
break;
case SORT:
SortContact(&contact);
break;
case PRINT:
PrintContact(&contact);
break;
case EXIT:
DestorContact(&contact);
printf("程序结束\n");
break;
default:
printf("选择错误,请重新选择:");
}
} while (input);
return 0;
}
四、注意事项
1.删除联系人时,可判断现有联系人数量所占通讯录容量的比例来决定是否删除对应的多余空间,以免造成内存浪费。
2.在关闭程序的时候,需要将占用的堆区空间释放并置空,若未释放,则程序关闭时堆区的内存仍将存有通讯录的信息,多次执行程序将可能造成内存溢出,应避免这种情况!!!
3.存放当前版本通讯录的gitee链接