铁子们好啊!今天咱们来整一个有意思的玩意——通讯录,相信大家对通讯录并不陌生,那接下来就跟着阿辉把它拿捏了
铁子们都知道通讯录是用来存放联系人信息的,首先我们得定义一个结构体来描述联系人的各项特征,比如:姓名、年龄、性别、电话号、地址等。
#define NAME_MAX 20 //姓名
#define SEX_MAX 5 //性别
#define TELE_MAX 12 //电话
#define ADDR_MAX 30 //地址
//联系人信息
typedef struct people
{
char name[NAME_MAX];
int age;
char sex[SEX_MAX];
char numb[TELE_MAX];
char addr[ADDR_MAX];
}peo;//重定义
对于通讯录这种数据我们可以通过下面这样的结构体来实现:
//通讯录
typedef struct contact
{
peo* data;
int size;//记录管理联系人个数
int capacity;//记录通讯录容量
}contact;
实际上,上述通讯录的本质是一个顺序表,怎么理解呢?给铁子们上图
有了数组的首元素地址data
、数组的元素个数size
以及数组空间的大小capacity
,我们可以轻易的管理数组中的元素以及数组的大小
对于通讯录我们要实现六大功能的接口函数:增加联系人信息、删除联系人信息、查找联系人信息、修改联系人信息、打印通讯录以及对通讯录信息排序。
不过为了实现用户和计算机的交互我们首先要实现一个菜单来供用户选择:
void menu()
{
printf("------------------------------\n");
printf("---- 1.添加联系人 ----\n");
printf("---- 2.删除联系人 ----\n");
printf("---- 3.查找联系人 ----\n");
printf("---- 4.修改联系人 ----\n");
printf("---- 5.打印通讯录 ----\n");
printf("---- 6.排序通讯录 ----\n");
printf("---- 0.退出通讯录 ----\n");
printf("------------------------------\n");
}
有了以上的准备我们可以搭建出通讯录的框架,同样这里我们使用模块化的方式进行设计
通讯录分为test.c、contact.c两个源文件和contact.h一个头文件:
test.c:主函数接口引入
contact.c:函数功能的实现
contact.h:头文件引入、函数声明、结构体声明
contact.h头文件
#include
#include
#include
#define NAME_MAX 20 //姓名
#define SEX_MAX 5 //性别
#define TELE_MAX 12 //电话
#define ADDR_MAX 30 //地址
//联系人信息
typedef struct people
{
char name[NAME_MAX];
int age;
char sex[SEX_MAX];
char numb[TELE_MAX];
char addr[ADDR_MAX];
}peo;
//通讯录
typedef struct contact
{
peo* data;
int size;//管理联系人
int capacity;//记录通讯录容量
}contact;
//接口函数实现通讯录各项功能
//增加联系人
void AddCon(contact* p);
//删除联系人
void DeteleCon(contact* p);
//查找联系人
void SearchCon(contact* p);
//修改联系人
void ModifyCon(contact* p);
//打印通讯录
void PrintCon(contact* p);
//释放通讯录内存
void DistroyCon(contact* p);
//给联系人排序
void SortCon(contact* p);
test.c源文件
#include"contact.h"
enum Op//使用枚举维护代码的可读性
{
Exit,//0
Add,//1
Delete,//2
Search,//3
Modify,//4
Print,//5
sort,//6
};
void menu()//打印菜单
{
printf("------------------------------\n");
printf("---- 1.添加联系人 ----\n");
printf("---- 2.删除联系人 ----\n");
printf("---- 3.查找联系人 ----\n");
printf("---- 4.修改联系人 ----\n");
printf("---- 5.打印通讯录 ----\n");
printf("---- 6.排序通讯录 ----\n");
printf("---- 0.退出通讯录 ----\n");
printf("------------------------------\n");
}
int main()
{
//声明通讯录变量con并初始化通讯录
contact con = { NULL, 0, 0 };
int input = 0;
do {
menu();
printf("请选择要使用的功能:>");
scanf("%d", &input);
switch (input)
{
case Add:
AddCon(&con);
break;
case Delete:
DeteleCon(&con);
break;
case Search:
SearchCon(&con);
break;
case Modify:
ModifyCon(&con);
break;
case Print:
PrintCon(&con);
break;
case sort:
SortCon(&con);
break;
case Exit:
DistroyCon(&con);//退出程序时释放内存
break;
default:
printf("输入错误,请重新输入!\n");
}
} while (input);
return 0;
}
有了上述的框架,我们只需要在contact.c源文件
中实现各项功能的接口函数通讯录就完成了
添加联系人接口:
void AddCon(contact* p)
{
if (p == NULL)//先判断传过来的指针是否为空
return;//为空直接返回
if (p->capacity == p->size)//判断容量与数组元素个数是否相等,相等说明数组该扩容了
{
//如果进来初始化数组还未分配空间,先让capacity+1
if (p->capacity == 0) p->capacity++;
//然后使用realloc开辟空间,后续数组空间不够直接增加一倍原本空间,一举两得
peo* pd = (peo*)realloc(p->data, sizeof(peo) * p->capacity);
if (pd == NULL)//空间开辟失败直接返回
return;
p->data = pd;//把开辟空间的首地址赋给data
p->capacity *= 2;//开辟完空间capacity倍增
}
printf("请输入姓名:>");
scanf("%s", p->data[p->size].name);
printf("请输入年龄:>");
scanf("%d", &(p->data[p->size].age));
printf("请输入姓别:>");
scanf("%s", p->data[p->size].sex);
printf("请输入电话:>");
scanf("%s", p->data[p->size].numb);
printf("请输入地址:>");
scanf("%s", p->data[p->size].addr);
printf("添加成功!\n");
p->size++;//联系人添加成功size自增
}
删除指定联系人接口:
先遍历数组找到要删除的元素,后续我们的查找修改联系人都需要遍历数组进行查找,这里我们封装好一个函数check
通过对比名字来实现
查找函数:
//这个函数我们使用static修饰,使其只能在源文件contact.c中使用
static int check(contact* p,char a[])
{
int i = 0;
for (i = 0; i < p->size; i++)
{
if (strcmp(a, p->data[i].name) == 0)
break;//找到后跳出循环
}
if (i >= p->size)//i如果超出size说明数组中没有改联系人
return -1;//返回-1
return i;//找到则返回该联系人下标
}
void DeteleCon(contact* p)
{
if (p == NULL)//判断穿入指针是否为空
return;
if (p->size == 0)//看数组中是否有元素
{
printf("空的我删个锤子啊!\n");
return;
}
char str[20] = {0};//输入要删除的联系人的姓名
printf("请输入要删除联系人的名字:>");
scanf("%s", str);
int ret = check(p, str);//查找要删除联系人的下标
if (ret == -1)
{
printf("查无此人!\n");
return;
}
//上述图片的功能,至删除元素起后一个元素覆盖前一个元素
for (int i = ret; i < p->size - 1; i++)
{
p->data[i] = p->data[i + 1];
}
printf("已成功删除!\n");
p->size--;//size自减
}
查找指定联系人接口:
这个很简单
void SearchCon(contact* p)
{
if (p == NULL)
return;
if (p->size == 0)
{
printf("空的我查个锤子!\n");
return;
}
char str[20] = { 0 };
printf("请输入要查找的联系人的名字:>");
scanf("%s", str);
int ret = check(p, str);//查找要找的联系人的下标
if (ret == -1)
{
printf("查无此人!\n");
return;
}
//打印要找的联系人的信息
printf("%-15s %-5d %-8s %-12s %-20s\n",
p->data[ret].name,
p->data[ret].age,
p->data[ret].sex,
p->data[ret].numb,
p->data[ret].addr);
}
修改指定联系人接口:
这个也很简单,找到后修改
void ModifyCon(contact* p)
{
if (p == NULL)
return;
if(p->size == 0)
{
printf("空的我改个锤子!\n");
return;
}
char str[20] = { 0 };
printf("请输入要修改的联系人的名字:>");
scanf("%s", str);
int ret = check(p, str);//查找要找的联系人的下标
if (ret == -1)
{
printf("查无此人!\n");
return;
}
//修改联系人内容
printf("请输入姓名:>");
scanf("%s", p->data[ret].name);
printf("请输入年龄:>");
scanf("%d", &(p->data[ret].age));
printf("请输入姓别:>");
scanf("%s", p->data[ret].sex);
printf("请输入电话:>");
scanf("%s", p->data[ret].numb);
printf("请输入地址:>");
scanf("%s", p->data[ret].addr);
printf("修改成功!\n");
}
打印联系人接口:
打印就更简单了
void PrintCon(contact* p)
{
if (p == NULL)
return;
//下面这一行是为了便于观察
printf("%-15s %-5s %-8s %-12s %-20s\n", "名字", "年龄", "性别", "电话", "地址");
for (int i = 0; i < p->size; i++)//遍历数组打印
{
printf("%-15s %-5d %-8s %-12s %-20s\n",
p->data[i].name,
p->data[i].age,
p->data[i].sex,
p->data[i].numb,
p->data[i].addr);
}
}
排序联系人接口:
//比较器,利用名字字典序排列
int cmp(const void* p1,const void* p2)
{
return strcmp(((peo*)p1)->name, ((peo*)p2)->name);
}
void SortCon(contact* p)
{
if (p == NULL)
return;
if (p->size < 2)//元素小于两个不需要排序
return;
//利用库函数qsort排序,得自己定义比较器
qsort(p->data, p->size, sizeof(p->data[0]), cmp);
printf("排序完成!\n");
}
释放通讯录空间接口:
void DistroyCon(contact* p)
{
free(p->data);//释放动态内存
p->data = NULL;//指针置空
//下面变量置0
p->size = 0;
p->capacity = 0;
printf("内存释放!\n");
}
到这里动态通讯录也是实现完成了,并不是很难相信大家都能轻易拿下,希望这篇博客能让大家有所收获, 如果觉得阿辉写得不错的话,记得给个赞呗,你们的支持是我创作的最大动力