目录
通讯录1.0里的 PeoInfo data[MAX] 一次开辟定了内存空间。我放150个人空间不够。只放15个人又太浪费空间。
我们对通讯录1.0进行改造升级。
扩容:方便测试,先给3个内存空间,以后当前联系人数量 == 最大联系人数量时,最大容量增加2个内存空间。
减容:当最大人数比现有人数多5个以上,减容最大人数3个内存空间。
一.引入枚举
test.c 的 switch 语句中的 case 1: case 2: ......我们写代码时很容易忘记这些 1,2......分别代表什么含义。
枚举常量的下标默认从0开始,每次增加1 。我们引入枚举常量。
这样在 case x:这里就能看到想要干什么。
test.c
- enum Option
- {
- EXIT,//0
- ADD, //1
- DEL, //2
- SEARCH,
- MODIFY,
- SHOW,
- SORT,
- DELALL
- };
-
- switch (input)
- {
- case ADD:
- AddContact(&con);
- break;
- case DEL:
- DelContact(&con);
- break;
- case SEARCH:
- SearchContact(&con);
- break;
- case MODIFY:
- ModifyContact(&con);
- break;
- case SHOW:
- ShowContact(&con);
- break;
- case SORT:
- break;
- case DELALL:
- DelAllContact(&con);
- break;
- case EXIT:
- printf("退出通讯录\n");
- break;
- default:
- printf("输入错误\n");
- break;
- }
二.动态内存开辟
我们要改造,PeoInfo data[MAX] 这里就不能是数组了。
contact.h
- //静态版本
- typedef struct Contact
- {
- PeoInfo data[MAX];//存放人的信息
- int sz;//当前已经存入联系人的个数
- }Contact;
我们要用 malloc 开辟一块空间,把地址存起来。
所以,要把 data 改为 PeoInfo * 类型的指针。由 data 指向开辟的空间。data 指向空间里存放人的信息。
除了 data,sz ,还需要记录当前最大容量。
- #include
-
- #define DEFAULT_SZ 3 //通讯录默认大小
- #define INC_SZ 2 //每次增加
- #define RED_SZ 3 //每次减少
- #define DIFF_SZ_CAPA 5 //最大 - 当前 的差值
-
- //动态版本
- typedef struct Contact
- {
- PeoInfo* data;//指向存放人信息的空间
- int sz;//当前已经存入联系人的个数
- int capacity;//当前通讯录的最大容量
- }Contact;
现在data 还没有指向的空间。
1.初始化
contant.c
- //静态版本
- void InitContact(Contact* pc)
- {
- assert(pc);
- pc->sz = 0;
- memset(pc->data, 0, sizeof(pc->data));
- }
pc->sz = 0;肯定是不用变的。这里就不能用 memset 了,要用 malloc 开辟内存空间。
我们先开辟空间,再指定通讯录默认大小为3 。这样更合理。
- //动态版本
- void InitContact(Contact* pc)
- {
- assert(pc);
- pc->sz = 0;
- pc->data = (PeoInfo*)malloc(sizeof(PeoInfo) * DEFAULT_SZ);
- pc->capacity = DEFAULT_SZ;
- }
malloc 的参数是要开辟内存的大小(字节)。
所以我们代码中 malloc 参数部分是:一个人的大小 * 通讯录默认人数 = 要开辟字节数
malloc 返回类型 void* 。用 pc 指向的 data 指针接收。data 的类型:PeoInfo * 。所以强制转换。
malloc 是不对开辟的内存空间初始化的。上面这样写,我们要初始化。
所以我用 calloc 。它初始化内存空间的每个字节为0
为 num 个大小为 size 的元素开辟内存空间。
- //动态版本
- void InitContact(Contact* pc)
- {
- assert(pc);
- pc->sz = 0;
- pc->data = (PeoInfo*)calloc(DEFAULT_SZ, sizeof(PeoInfo));
- pc->capacity = DEFAULT_SZ;
- }
同时,我们要对开辟的空间进行维护,判断是否开辟成功。
直接用 pc->data 接收不合适
最终代码
- //动态版本
- void InitContact(Contact* pc)
- {
- assert(pc);
- pc->sz = 0;
- PeoInfo* ptr = (PeoInfo*)calloc(DEFAULT_SZ, sizeof(PeoInfo));
- if (ptr == NULL)
- {
- perror("InitContact:calloc");
- return;
- }
- pc->data = ptr;
- pc->capacity = DEFAULT_SZ;
- }
2.扩容
contact.c
改为动态开辟版本,里面放了元素,放不下时,要考虑扩容问题。对 Add 更改。
- //静态版本
- void AddContact(Contact* pc)
- {
- assert(pc);
- if (pc->sz == MAX)
- {
- printf("通讯录已满,无法添加\n");
- return;
- }
- //增加一个人的信息
- printf("请输入名字:");......
-
- pc->sz++;
- }
动态版本不存在放满的情况,这里不用判断 == MAX
每次添加联系人后进行判断,当最大空间人数 capacity == 当前空间人数 sz 时,扩容。
- //动态版本
- void AddContact(Contact* pc)
- {
- assert(pc);
- //增加一个人的信息......
- if (pc->sz == pc->capacity)
- {
- //扩容
- }
- pc->sz++;
- }
用 realloc 对 data 指向的空间调整。扩容的地方分装成函数。
ptr 是要调整的内存地址 size 是调整后的新大小(字节)。返回开辟好新空间的起始地址。
若当最大空间人数 capacity != 当前空间人数 sz 时,不扩容,直接返回。
新大小(字节) = 增加后的人数 * 每个人的大小。
判断是否成功开辟新空间,若是,再将 ptr 传给 pc->data
最终代码
- void check_capacity(Contact* pc)
- {
- assert(pc);
- if (pc->sz == pc->capacity)
- {
- //扩容
- PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));
- if (ptr == NULL)
- {
- perror("check_capacity:realloc");
- return;
- }
- pc->data = ptr;
- pc->capacity += INC_SZ;//当前通讯录的最大容量再自增2
- }
- }
-
- //动态版本
- void AddContact(Contact* pc)
- {
- assert(pc);
-
- //增加一个人的信息
- printf("请输入名字:");
- scanf("%s", pc->data[pc->sz].name);
- printf("请输入年龄:");
- scanf("%d", &(pc->data[pc->sz].age));
- printf("请输入性别:");
- scanf("%s", pc->data[pc->sz].sex);
- printf("请输入地址:");
- scanf("%s", pc->data[pc->sz].addr);
- printf("请输入电话:");
- scanf("%s", pc->data[pc->sz].tele);
-
- pc->sz++;
- check_capacity(pc);
-
- printf("\n当前联系人数量:%d\n", pc->sz);
- printf("当前最大容量:%d\n", pc->capacity);
- }
3.退出自动销毁空间
当程序退出时,空间要销毁,free
- //test.c
- case EXIT:
- DestroyContact(&con);
- printf("退出通讯录\n");
- break;
-
- //contact.h 销毁内存空间
- void DestroyContact(Contact* pc);
-
- //contact.c
- void DestroyContact(Contact* pc)
- {
- assert(pc);
- free(pc->data);
- pc->data = NULL;
- pc->capacity = 0;
- pc->sz = 0;
- pc = NULL;
- }
4.减容
我们还是要判断通讯录是否为空,为空无法删除。
只需在 chack_capacity 函数中加上减容部分,再在 DelContact 函数中加几条语句即可。
最终代码
- void check_capacity(Contact* pc)
- {
- assert(pc);
- if (pc->sz == pc->capacity)
- {
- //扩容
- }
-
- if ((pc->capacity - pc->sz) > DIFF_SZ_CAPA)
- {
- //减容
- PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity - RED_SZ) * sizeof(PeoInfo));
- if (ptr == NULL)
- {
- perror("check_capacity:realloc");
- return;
- }
- pc->data = ptr;
- pc->capacity -= RED_SZ;
- }
- }
-
- //动态版本
- void DelContact(Contact* pc)
- {
- assert(pc);
-
- char name[NAME_MAX] = { 0 };
- if (pc->sz == 0)
- {
- printf("通讯录为空,无法删除\n");
- return;
- }
- //查找
- printf("请输入要删除的人的名字:>");
- scanf("%s", name);
- int ret = FindByName(pc, name);
- if (-1 == ret)
- {
- printf("要删除的人不存在\n");
- return;
- }
-
- int i = 0;
- //删除
- for (i = ret; i < pc->sz - 1; i++)
- {
- pc->data[i] = pc->data[i + 1];
- }
- pc->sz--;
- printf("删除成功\n");
- check_capacity(pc);
-
- printf("\n当前联系人数量:%d\n", pc->sz);
- printf("当前最大容量:%d\n", pc->capacity);
- }
三.依姓名排序
用 qsort 函数。(点击 qsort 查看详细说明)
- //contact.c
- int sortcon_byname(const void* e1, const void* e2)
- {
- return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
- }
-
- void SortContact_ByName(Contact* pc)
- {
- assert(pc);
- qsort(pc->data, pc->sz, sizeof(PeoInfo), sortcon_byname);
- printf("排序成功\n");
- }
-
- //test.c
- case SORT:
- SortContact_ByName(&con);
- break;
- //contact.h
- void SortContact_ByName(Contact* pc);
错误写法:
- int sortcon_byname(const void* e1, const void* e2)
- {
- return strcmp(((Contact*)e1)->data->name, ((Contact*)e2)->data->name);
- }
e1,e2 分别是要比较的两个元素的地址
比较的是人的信息PeoInfo
我们发现,2.0版本的通讯录,程序关闭,不保存这次运行时Add的联系人。
下次运行,按5显示联系人时,是空白。
怎么解决?
期待通讯录3.0
评论记录:
回复评论: