通讯录的实现及动态内存管理
- 1.通讯录实现要求
- 2.静态版通讯录实现
- 2.1 contact.h文件实现
- 2.2 contact.c文件实现
- 2.3 main.c文件实现
- 2.4 静态版通讯录全部文件代码
- 3.动态内存管理
- 3.1 为什么存在动态内存分配
- 3.2 动态内存函数的介绍
- 3.3 常见的动态内存错误
- 3.4 C/C++程序的内存开辟
- 3.5 柔性数组
- 4.动态版通讯录实现
- 4.1 contact.h文件改动
- 4.2 contact.c文件改动
- 4.3 main.c文件改动
- 4.4 动态版通讯录全部文件代码
- 5.结语
1.通讯录实现要求
通讯录可以用来存储1000个人的信息,每个人的信息包括:姓名、性别、年龄、电话、住址
提供方法:
- 添加联系人信息
- 删除指定联系人信息
- 查找指定联系人信息
- 修改指定联系人信息
- 显示所有联系人信息
- 清空所有联系人
- 以名字排序所有联系人
2.静态版通讯录实现
整个程序的实现需要建立三个文件
contact.h
通讯录的结构体定义及函数的声明
contact.c
函数的实现
main.c
主函数调用
2.1 contact.h文件实现
头文件引用和宏定义
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
#define NAME_MAX 10 //名字的长度(单位:字节)
#define SEX_MAX 6 //性别长度
#define ADDR_MAX 20 //住址长度
#define TELE_MAX 12 //电话长度
#define N 100 //通讯录的长度(单位为个人结构体大小)
结构体定义
typedef struct people //个人结构体定义
{
char name[NAME_MAX]; //姓名
char sex[SEX_MAX]; //性别
int age; //年龄
char tele[TELE_MAX]; //电话
char addr[ADDR_MAX]; //住址
}peo;
typedef struct Contact //通讯录结构体定义
{
peo data[N]; //个人结构体数组
int sz; //结构体长度
}Con;
枚举定义,方便主函数switch语句调用,易分辨。
enum
{
EXIT,//退出
ADD,//增加
DEL,//删除
MOD,//修改
SEARCH,//查找
SHOW,//打印
DISTORY,//删除全部
QSORT//以名称排序
};
函数声明
void InitCon(Con* cont);//初始化函数
void add(Con* cont);//增加联系人
void show(const Con* cont);//打印联系人
int FindName(const Con* cont, const char* name);//查找联系人(内含)
void del(Con* cont);//删除联系人
void mod(Con* cont);//修改联系人
void sel(const Con* cont);//查找联系人打印
void Distory(Con* cont);//删除所有联系人
int cmp_stu_by_name(const void* p1, const void* p2);//排序调用的回调函数
下面我们来看最关键的部分,即为函数定义
2.2 contact.c文件实现
首先我们要包含头文件#include "contact.h"
初始化函数InitCon
void InitCon(Con* cont)
{
assert(cont);
cont->sz = 0;
memset(cont->data, 0, sizeof(cont->data));
}
开头我们先断言,防止传NULL指针,第二步初始化长度为0,最后将结构体数组中的元素均初始化为0即可。
添加联系人函数add
void add(Con* cont)
{
assert(cont);
if (cont->sz == N)
{
printf("添加失败,通讯录已满");
return;
}
printf("请输入要添加联系人的信息:\n");
printf("请输入姓名:");
scanf("%s", cont->data[cont->sz].name);
printf("请输入性别:");
scanf("%s", cont->data[cont->sz].sex);
printf("请输入年龄:");
scanf("%d", &cont->data[cont->sz].age);
printf("请输入电话:");
scanf("%s", cont->data[cont->sz].tele);
printf("请输入地址:");
scanf("%s", cont->data[cont->sz].addr);
cont->sz++;
printf("添加成功\n");
}
开始一样,我们先断言防止传NULL指针,第二就是判断空间有没有满,如果满了,直接返回退出并提示,如果没满,则继续添加联系人信息,添加完之后提示添加成功,再将sz++。
显示联系人函数show
void show(const Con* cont)
{
assert(cont);
int i = 0;
printf(" 通讯录\n");
printf(" +---------------------------------------------------------------+\n");
printf(" |姓名 性别 年龄 电话 地址 |\n");
for (i = 0; i < cont->sz; i++)
{
printf(" |%-10s %-5s %-5d %-11s %-15s |\n", cont->data[i].name, cont->data[i].sex, cont->data[i].age, cont->data[i].tele, cont->data[i].addr);
}
printf(" +---------------------------------------------------------------+\n");
}
首先一样,我们先断言防止传NULL指针,第二就是打印题头,然后对结构体数组进行遍历打印,注意对齐,根据不同情况可自行调整。
查找联系人函数(内含)FindName
int FindName(const Con* cont, const char* name)//查找人函数
{
assert(cont && name);//防止传入空指针
int i = 0;
for (i = 0; i < cont->sz; i++)
{
if (!strcmp(cont->data[i].name, name))//查找通讯录是否有这个名字
{
return i;//有就返回下标
}
}
return -1;//没有返回一个负数.
}
实现这个函数主要是为了后面的删除、修改和查找并打印函数,方便复用,减少代码冗余。
首先同样,我们先断言防止传NULL指针,然后遍历使用strcmp函数进行字符匹配,查看是否有这个名字,有就返回下标,没有就返回负数。
删除联系人函数del
void del(Con* cont)//删除联系人函数
{
assert(cont);
int i = 0;
char name[NAME_MAX];
printf("请输入需要删除的联系人的姓名:\n");
scanf("%s", name);
i = FindName(cont, name);
if (i == -1)
{
printf("通讯录中没有该联系人,删除失败\n");
return;
}
for (; i < cont->sz - 1; i++)
{
cont->data[i] = cont->data[i + 1];
}
cont->sz--;
printf("删除成功,姓名为%s的联系人已删除\n", name);
}
首先同样,我们先断言防止传NULL指针,然后创建一个字符数组,方便输入姓名,再复用上面的查找函数,如果返回-1,则说明没有这个联系人,提示后返回退出,如果有,则按照其返回的下标向后遍历依次向前覆盖。最后sz–。
修改联系人函数mod
void mod(Con* cont)//修改联系人函数
{
assert(cont);
int ret = 0;
char name[NAME_MAX];
printf("请输入要修改的联系人的姓名:\n");
scanf("%s", name);
ret = FindName(cont, name);
if (ret == -1)
{
printf("通讯录中没有该联系人,修改失败\n");
return;
}
printf("请输入修改后联系人的信息:\n");
printf("请输入姓名:");
scanf("%s", cont->data[ret].name);
printf("请输入性别:");
scanf("%s", cont->data[ret].sex);
printf("请输入年龄:");
scanf("%d", &cont->data[ret].age);
printf("请输入电话:");
scanf("%s", cont->data[ret].tele);
printf("请输入地址:");
scanf("%s", cont->data[ret].addr);
printf("修改成功\n");
}
首先同样,我们先断言防止传NULL指针,然后创建一个字符数组,方便输入姓名,再复用上面的查找函数,如果返回-1,则说明没有这个联系人,提示后返回退出,如果有,则同增加联系人依次修改,当然你也可以再进行优化,修改某一项。
查找联系人函数sel
void sel(const Con* cont)//查询联系人函数
{
char name[NAME_MAX];
printf("请输入要查询的联系人的名字:\n");
scanf("%s", name);
int ret = FindName(cont, name);
if (ret == -1)
{
printf("通讯录中没有该联系人,查询失败.\n");
return;
}
printf("查询成功,该联系信息如下:\n");
printf(" +---------------------------------------------------------------+\n");
printf(" |姓名 性别 年龄 电话 地址 |\n");
printf(" |%-10s %-5s %-5d %-11s %-15s |\n", cont->data[ret].name, cont->data[ret].sex, cont->data[ret].age, cont->data[ret].tele, cont->data[ret].addr);
printf(" +---------------------------------------------------------------+\n");
}
首先我们创建一个字符数组,方便输入姓名,再复用上面的查找函数,如果返回-1,则说明没有这个联系人,提示后返回退出,如果有,则同show函数打印,只不过这里只打印一个。
删除所有联系人函数Distory
void Distory(Con* cont)
{
assert(cont);
cont->sz = 0;
printf("全部联系人删除成功!\n");
}
删除全部联系人,只需要将sz归0即可,下一次添加就会直接覆盖,而且也不能再访问之前的联系人数据了。
排序联系人函数的回调函数cmp_stu_by_name
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((peo*)p1)->name, ((peo*)p2)->name);
}
这是调用库函数qsort给通讯录以姓名排序的一个回调函数,不理解的可以看我之前指针进阶那篇文章,其中就包含了qsort函数的使用及其回调函数的定义。
2.3 main.c文件实现
#include"contact.h"
void menu()
{
printf("\n 欢迎使用通讯录:\n");
printf(" +-------------------------------------------------------------+\n");
printf(" | 1.添加联系人 2.删除联系人 |\n");
printf(" | 3.修改联系人 4.查询联系人 |\n");
printf(" | 5.展示通讯录 6.删除全部联系人 |\n");
printf(" | 7.排序联系人 0.退出通讯录 |\n");
printf(" +-------------------------------------------------------------+\n");
printf("请选择:");
}
int main()
{
int input = 0;
Con cont;
InitCon(&cont);
while (1) {
do
{
menu();
scanf("%d", &input);
switch (input)
{
case EXIT:
printf("退出通讯录");
break;
case ADD:
add(&cont);
break;
case DEL:
del(&cont);
break;
case MOD:
mod(&cont);
break;
case SEARCH:
sel(&cont);
break;
case SHOW:
show(&cont);
break;
case DISTORY:
Distory(&cont);
break;
case QSORT:
qsort(cont.data, cont.sz, sizeof(peo), cmp_stu_by_name);
printf("排序成功\n");
break;
default:
printf("输入错误");
break;
}
} while (input);
break;
}
return 0;
}
这里首先就是一个菜单排版,然后就是switch语句对各函数调用,这里的每个case选项用的是前面枚举定义的各个名称方便辨别。
2.4 静态版通讯录全部文件代码
//contact.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
#define NAME_MAX 10
#define SEX_MAX 6
#define ADDR_MAX 20
#define TELE_MAX 12
#define N 100
typedef struct people
{
char name[NAME_MAX];
char sex[SEX_MAX];
int age;
char tele[TELE_MAX];
char addr[ADDR_MAX];
}peo;
typedef struct Contact
{
peo data[N];
int sz;
}Con;
enum
{
EXIT,
ADD,
DEL,
MOD,
SEARCH,
SHOW,
DISTORY,
QSORT
};
InitCon(Con* cont);
void add(Con* cont);
void show(const Con* cont);
int FindName(const Con* cont, const char* name);
void del(Con* cont);
void mod(Con* cont);
void sel(const Con* cont);
void Distory(Con* cont);
int cmp_stu_by_name(const void* p1, const void* p2);
//contact.c
#include "contact.h"
InitCon(Con* cont)
{
assert(cont);
cont->sz = 0;
memset(cont->data, 0, sizeof(cont->data));
}
void add(Con* cont)
{
assert(cont);
if (cont->sz == N)
{
printf("添加失败,通讯录已满");
return;
}
printf("请输入要添加联系人的信息:\n");
printf("请输入姓名:");
scanf("%s", cont->data[cont->sz].name);
printf("请输入性别:");
scanf("%s", cont->data[cont->sz].sex);
printf("请输入年龄:");
scanf("%d", &cont->data[cont->sz].age);
printf("请输入电话:");
scanf("%s", cont->data[cont->sz].tele);
printf("请输入地址:");
scanf("%s", cont->data[cont->sz].addr);
cont->sz++;
printf("添加成功\n");
}
void show(const Con* cont)
{
assert(cont);
int i = 0;
printf(" 通讯录\n");
printf(" +---------------------------------------------------------------+\n");
printf(" |姓名 性别 年龄 电话 地址 |\n");
for (i = 0; i < cont->sz; i++)
{
printf(" |%-10s %-5s %-5d %-11s %-15s |\n", cont->data[i].name, cont->data[i].sex, cont->data[i].age, cont->data[i].tele, cont->data[i].addr);
}
printf(" +---------------------------------------------------------------+\n");
}
int FindName(const Con* cont, const char* name)//查找人函数
{
assert(cont && name);//防止传入空指针
int i = 0;
for (i = 0; i < cont->sz; i++)
{
if (!strcmp(cont->data[i].name, name))//查找通讯录是否有这个名字
{
return i;//有就返回下标
}
}
return -1;//没有返回一个负数.
}
void del(Con* cont)//删除联系人函数
{
assert(cont);//防止传入空指针
int i = 0;
char name[NAME_MAX];
printf("请输入需要删除的联系人的姓名:\n");
scanf("%s", name);
i = FindName(cont, name);
if (i == -1)
{
printf("通讯录中没有该联系人,删除失败\n");
return;
}
for (; i < cont->sz - 1; i++)//注意这里sz要-1,因为下面用到了i+1下标
{
cont->data[i] = cont->data[i + 1];
}
cont->sz--;
printf("删除成功,姓名为%s的联系人已删除\n", name);
}
void mod(Con* cont)//修改联系人函数
{
assert(cont);//防止传入空指针
int ret = 0;
char name[NAME_MAX];
printf("请输入要修改的联系人的姓名:\n");
scanf("%s", name);
ret = FindName(cont, name);
if (ret == -1)
{
printf("通讯录中没有该联系人,修改失败\n");
return;
}
printf("请输入修改后联系人的信息:\n");
printf("请输入姓名:");
scanf("%s", cont->data[ret].name);
printf("请输入性别:");
scanf("%s", cont->data[ret].sex);
printf("请输入年龄:");
scanf("%d", &cont->data[ret].age);
printf("请输入电话:");
scanf("%s", cont->data[ret].tele);
printf("请输入地址:");
scanf("%s", cont->data[ret].addr);
printf("修改成功.\n");
}
void sel(const Con* cont)//查询联系人函数
{
char name[NAME_MAX];
printf("请输入要查询的联系人的名字:\n");
scanf("%s", name);
int ret = FindName(cont, name);
if (ret == -1)
{
printf("通讯录中没有该联系人,查询失败.\n");
return;
}
printf("查询成功,该联系信息如下:\n");
printf(" +---------------------------------------------------------------+\n");
printf(" |姓名 性别 年龄 电话 地址 |\n");
printf(" |%-10s %-5s %-5d %-11s %-15s |\n", cont->data[ret].name, cont->data[ret].sex, cont->data[ret].age, cont->data[ret].tele, cont->data[ret].addr);
printf(" +---------------------------------------------------------------+\n");
}
void Distory(Con* cont)
{
assert(cont);
cont->sz = 0;
printf("全部联系人删除成功!\n");
}
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((peo*)p1)->name, ((peo*)p2)->name);
}
//main.c
#include"contact.h"
void menu()
{
printf("\n 欢迎使用通讯录:\n");
printf(" +-------------------------------------------------------------+\n");
printf(" | 1.添加联系人 2.删除联系人 |\n");
printf(" | 3.修改联系人 4.查询联系人 |\n");
printf(" | 5.展示通讯录 6.删除全部联系人 |\n");
printf(" | 7.排序联系人 0.退出通讯录 |\n");
printf(" +-------------------------------------------------------------+\n");
printf("请选择:");
}
int main()
{
int input = 0;
Con cont;
InitCon(&cont);
while (1) {
do
{
menu();
scanf("%d", &input);
switch (input)
{
case EXIT:
printf("退出通讯录");
break;
case ADD:
add(&cont);
break;
case DEL:
del(&cont);
break;
case MOD:
mod(&cont);
break;
case SEARCH:
sel(&cont);
break;
case SHOW:
show(&cont);
break;
case DISTORY:
Distory(&cont);
break;
case QSORT:
qsort(cont.data, cont.sz, sizeof(peo), cmp_stu_by_name);
printf("排序成功\n");
break;
default:
printf("输入错误");
break;
}
} while (input);
break;
}
return 0;
}
3.动态内存管理
3.1 为什么存在动态内存分配
我们已经掌握的内存开辟方式有:
int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间
但是上述的开辟空间的方式有两个特点:
- 空间开辟大小是固定的。
- 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。这时候就只能试试动态存开辟了。
3.2 动态内存函数的介绍
malloc和free
C语言提供了一个动态内存开辟的函数:
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
- 如果开辟成功,则返回一个指向开辟好空间的指针。
- 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
- 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
- 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:
void free (void* ptr);
free函数用来释放动态开辟的内存。
- 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
- 如果参数 ptr 是NULL指针,则函数什么事都不做。
malloc和free都声明在 stdlib.h 头文件中。
calloc
C语言还提供了一个函数叫 calloc , calloc 函数也用来动态内存分配。原型如下:
void* calloc (size_t num, size_t size);
- 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
- 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
举个例子:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (NULL != p)
{
}
free(p);
p = NULL;
return 0;
}
所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。
realloc
- realloc函数的出现让动态内存管理更加灵活。
- 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
函数原型如下:
void* realloc (void* ptr, size_t size);
- ptr 是要调整的内存地址
- size 调整之后新大小
- 返回值为调整之后的内存起始位置。
- 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。
- realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有足够大的空间
情况2:原有空间之后没有足够大的空间
情况1
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况2
当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。
由于上述的两种情况,realloc函数的使用就要注意一些。
3.3 常见的动态内存错误
对NULL指针的解引用操作
void test()
{
int* p = (int*)malloc(INT_MAX / 4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
对动态开辟空间的越界访问
void test()
{
int i = 0;
int* p = (int*)malloc(10 * sizeof(int));
if (NULL == p)
{
exit(EXIT_FAILURE);
}
for (i = 0; i <= 10; i++)
{
*(p + i) = i;//当i是10的时候越界访问
}
free(p);
}
对非动态开辟内存使用free释放
void test()
{
int a = 10;
int *p = &a;
free(p);//ok?
}
使用free释放一块动态开辟内存的一部分
void test()
{
int* p = (int*)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
对同一块动态内存多次释放
void test()
{
int* p = (int*)malloc(100);
free(p);
free(p);//重复释放
}
动态开辟内存忘记释放(内存泄漏)
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while (1);
}
忘记释放不再使用的动态开辟的空间会造成内存泄漏。
切记:
动态开辟的空间一定要释放,并且正确释放。
3.4 C/C++程序的内存开辟
C/C++程序内存分配的几个区域:
- 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
- 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
- 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
- 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁,所以生命周期变长。
3.5 柔性数组
也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
例如:
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
或有些编译器会报错无法编译可以改成:
typedef struct st_type
{
int i;
int a[];//柔性数组成员
}type_a;
柔性数组的特点:
- 结构中的柔性数组成员前面必须至少一个其他成员。
- sizeof 返回的这种结构大小不包括柔性数组的内存。
- 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
例如:
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4
柔性数组的使用
int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//业务处理
p->i = 100;
for(i=0; i<100; i++)
{
p->a[i] = i;
}
free(p);
这个柔性数组成员a,相当于获得了100个整型元素的连续空间。
柔性数组的优势
上述的 type_a 结构也可以设计为:
typedef struct st_type
{
int i;
int* p_a;
}type_a;
type_a* p = (type_a*)malloc(sizeof(type_a));
p->i = 100;
p->p_a = (int*)malloc(p->i * sizeof(int));
//业务处理
for (i = 0; i < 100; i++)
{
p->p_a[i] = i;
}
//释放空间
free(p->p_a);
p->p_a = NULL;
free(p);
p = NULL;
上述 代码1 和 代码2 可以完成同样的功能,但是 代码1 的实现有两个好处:
第一个好处是:方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
第二个好处是:这样有利于访问速度
连续的内存有益于提高访问速度,也有益于减少内存碎片。
4.动态版通讯录实现
我们就在原有的静态表上改动即可。
4.1 contact.h文件改动
#define N 3
#define INC 2
typedef struct Contact
{
peo* data;
int capacity;
int sz;
}Con;
void DistoryCon(Con* cont);
4.2 contact.c文件改动
通讯录初始化函数InitCon修改
void InitCon(Con* cont)
{
assert(cont);
cont->data = (peo*)malloc(N * sizeof(peo));
if (cont->data == NULL)
{
perror("malloc");
return;
}
cont->sz = 0;
cont->capacity = N;
}
首先断言,之后我们给结构体数组分配初始值3个结构体大小空间,然后判断是否开辟成功,sz初始值为0,capacity初始值为我们开始设定的N也就是3。
增加检查容量函数
int CheckCapacity(Con* cont)
{
if (cont->sz == cont->capacity)
{
peo* tmp = (peo*)realloc(cont->data, (cont->capacity + INC) * sizeof(peo));
if (tmp == NULL)
{
perror("relloc");
return 0;
}
else
{
cont->data = tmp;
cont->capacity += INC;
printf("增容成功");
return 1;
}
}
return 1;
}
这里我们用于判断容量是否已满,如果已满,我们则使用realloc函数进行扩容,这时我们不能直接扩容,而是要先用临时指针,如果空间申请成功,再将地址赋给通讯录,如果未申请成功,则为NULL指针,提示后返回0,增容成功,tmp地址传给data,容量+2,并提示增容成功,返回1,无需增容也返回1.
增加联系人函数add修改
void add(Con* cont)
{
assert(cont);
if (0 == CheckCapacity(cont))
return;
printf("请输入要添加联系人的信息:\n");
printf("请输入姓名:");
scanf("%s", cont->data[cont->sz].name);
printf("请输入性别:");
scanf("%s", cont->data[cont->sz].sex);
printf("请输入年龄:");
scanf("%d", &cont->data[cont->sz].age);
printf("请输入电话:");
scanf("%s", cont->data[cont->sz].tele);
printf("请输入地址:");
scanf("%s", cont->data[cont->sz].addr);
cont->sz++;
printf("添加成功\n");
}
把之前的条件判断改为容量函数的复用,若为0,直接返回退出。
增加函数销毁动态申请的空间DistoryCon
void DistoryCon(Con* cont)
{
free(cont->data);
cont->data = NULL;
cont->capacity = 0;
cont->sz = 0;
}
这个函数是为了将已申请的空间还给内存,防止内存泄漏。
4.3 main.c文件改动
这个文件只有一处改动,就是在switch语句中的case 0处增加了销毁动态申请的空间函数DistoryCon调用
4.4 动态版通讯录全部文件代码
//contact.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
#define NAME_MAX 10
#define SEX_MAX 6
#define ADDR_MAX 20
#define TELE_MAX 12
#define N 3
#define INC 2
typedef struct people
{
char name[NAME_MAX];
char sex[SEX_MAX];
int age;
char tele[TELE_MAX];
char addr[ADDR_MAX];
}peo;
typedef struct Contact
{
peo* data;
int capacity;
int sz;
}Con;
enum
{
EXIT,
ADD,
DEL,
MOD,
SEARCH,
SHOW,
DISTORY,
QSORT
};
void InitCon(Con* cont);
void add(Con* cont);
void show(const Con* cont);
int FindName(const Con* cont, const char* name);
void del(Con* cont);
void mod(Con* cont);
void sel(const Con* cont);
void Distory(Con* cont);
int cmp_stu_by_name(const void* p1, const void* p2);
void DistoryCon(Con* cont);
//contact.c
#include "contact.h"
void InitCon(Con* cont)
{
assert(cont);
cont->data = (peo*)malloc(N * sizeof(peo));
if (cont->data == NULL)
{
perror("malloc");
return;
}
cont->sz = 0;
cont->capacity = N;
}
int CheckCapacity(Con* cont)
{
if (cont->sz == cont->capacity)
{
peo* tmp = (peo*)realloc(cont->data, (cont->capacity + INC) * sizeof(peo));
if (tmp == NULL)
{
perror("relloc");
return 0;
}
else
{
cont->data = tmp;
cont->capacity += INC;
printf("增容成功");
return 1;
}
}
return 1;
}
void add(Con* cont)
{
assert(cont);
if (0 == CheckCapacity(cont))
return;
printf("请输入要添加联系人的信息:\n");
printf("请输入姓名:");
scanf("%s", cont->data[cont->sz].name);
printf("请输入性别:");
scanf("%s", cont->data[cont->sz].sex);
printf("请输入年龄:");
scanf("%d", &cont->data[cont->sz].age);
printf("请输入电话:");
scanf("%s", cont->data[cont->sz].tele);
printf("请输入地址:");
scanf("%s", cont->data[cont->sz].addr);
cont->sz++;
printf("添加成功\n");
}
void show(const Con* cont)
{
assert(cont);
int i = 0;
printf(" 通讯录\n");
printf(" +---------------------------------------------------------------+\n");
printf(" |姓名 性别 年龄 电话 地址 |\n");
for (i = 0; i < cont->sz; i++)
{
printf(" |%-10s %-5s %-5d %-11s %-15s |\n", cont->data[i].name, cont->data[i].sex, cont->data[i].age, cont->data[i].tele, cont->data[i].addr);
}
printf(" +---------------------------------------------------------------+\n");
}
int FindName(const Con* cont, const char* name)//查找人函数
{
assert(cont && name);//防止传入空指针
int i = 0;
for (i = 0; i < cont->sz; i++)
{
if (!strcmp(cont->data[i].name, name))//查找通讯录是否有这个名字
{
return i;//有就返回下标
}
}
return -1;//没有返回一个负数.
}
void del(Con* cont)//删除联系人函数
{
assert(cont);//防止传入空指针
int i = 0;
char name[NAME_MAX];
printf("请输入需要删除的联系人的姓名:\n");
scanf("%s", name);
i = FindName(cont, name);
if (i == -1)
{
printf("通讯录中没有该联系人,删除失败\n");
return;
}
for (; i < cont->sz - 1; i++)//注意这里sz要-1,因为下面用到了i+1下标
{
cont->data[i] = cont->data[i + 1];
}
cont->sz--;
printf("删除成功,姓名为%s的联系人已删除\n", name);
}
void mod(Con* cont)//修改联系人函数
{
assert(cont);//防止传入空指针
int ret = 0;
char name[NAME_MAX];
printf("请输入要修改的联系人的姓名:\n");
scanf("%s", name);
ret = FindName(cont, name);
if (ret == -1)
{
printf("通讯录中没有该联系人,修改失败\n");
return;
}
printf("请输入修改后联系人的信息:\n");
printf("请输入姓名:");
scanf("%s", cont->data[ret].name);
printf("请输入性别:");
scanf("%s", cont->data[ret].sex);
printf("请输入年龄:");
scanf("%d", &cont->data[ret].age);
printf("请输入电话:");
scanf("%s", cont->data[ret].tele);
printf("请输入地址:");
scanf("%s", cont->data[ret].addr);
printf("修改成功.\n");
}
void sel(const Con* cont)//查询联系人函数
{
char name[NAME_MAX];
printf("请输入要查询的联系人的名字:\n");
scanf("%s", name);
int ret = FindName(cont, name);
if (ret == -1)
{
printf("通讯录中没有该联系人,查询失败.\n");
return;
}
printf("查询成功,该联系信息如下:\n");
printf(" +---------------------------------------------------------------+\n");
printf(" |姓名 性别 年龄 电话 地址 |\n");
printf(" |%-10s %-5s %-5d %-11s %-15s |\n", cont->data[ret].name, cont->data[ret].sex, cont->data[ret].age, cont->data[ret].tele, cont->data[ret].addr);
printf(" +---------------------------------------------------------------+\n");
}
void Distory(Con* cont)
{
assert(cont);
cont->sz = 0;
printf("全部联系人删除成功!\n");
}
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((peo*)p1)->name, ((peo*)p2)->name);
}
void DistoryCon(Con* cont)
{
free(cont->data);
cont->data = NULL;
cont->capacity = 0;
cont->sz = 0;
}
//main.c
#include"contact.h"
void menu()
{
printf("\n 欢迎使用通讯录:\n");
printf(" +-------------------------------------------------------------+\n");
printf(" | 1.添加联系人 2.删除联系人 |\n");
printf(" | 3.修改联系人 4.查询联系人 |\n");
printf(" | 5.展示通讯录 6.删除全部联系人 |\n");
printf(" | 7.排序联系人 0.退出通讯录 |\n");
printf(" +-------------------------------------------------------------+\n");
printf("请选择:");
}
int main()
{
int input = 0;
Con cont;
InitCon(&cont);
while (1) {
do
{
menu();
scanf("%d", &input);
switch (input)
{
case EXIT:
DistoryCon(&cont);
printf("退出通讯录");
break;
case ADD:
add(&cont);
break;
case DEL:
del(&cont);
break;
case MOD:
mod(&cont);
break;
case SEARCH:
sel(&cont);
break;
case SHOW:
show(&cont);
break;
case DISTORY:
Distory(&cont);
break;
case QSORT:
qsort(cont.data, cont.sz, sizeof(peo), cmp_stu_by_name);
printf("排序成功\n");
break;
default:
printf("输入错误");
break;
}
} while (input);
break;
}
return 0;
}
5.结语
有兴趣的小伙伴可以关注作者,如果觉得内容不错,请给个一键三连吧,蟹蟹你哟!!!
制作不易,如有不正之处敬请指出
感谢大家的来访,UU们的观看是我坚持下去的动力
在时间的催化剂下,让我们彼此都成为更优秀的人吧!!!