通讯录的主题思想,将100个人的信息存放在通讯录当中,对其进行增、删、改、查、排序等操作
1.静态版本
gitee码仓:静态版本实现通讯录的具体代码
首先人是复杂对象,需要用结构体进行存储
1.1 声明结构体对象
#define NAME_MAX 20
#define SEX_MAX 8
#define TELE_MAX 12
#define ADDR_MAX 30
#define DATA_MAX 100
// 存储人的信息
typedef struct Peo_info
{
char name[NAME_MAX];
int age;
char sex[SEX_MAX];
char tele[TELE_MAX];
char addr[ADDR_MAX];
}Peo_info;
// 存放通讯录的信息
typedef struct Contact
{
// 记录100个人的信息
Peo_info data[DATA_MAX];
// 通讯录当中人数
int count;
}Contact;
分别对 struct Peo_info 和 struct Contact 进行重命名操作,将元素空间用 #define 定义宏 增加代码可读性
1.2 基本框架
void menu()
{
printf("*********************************************\n");
printf("****** 1. add 2. del *******\n");
printf("****** 3. search 4. modify *******\n");
printf("****** 5. show 6. sort *******\n");
printf("****** 0. exit *******\n");
printf("*********************************************\n");
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择要进行的操作:");
scanf("%d", &input);
switch (input)
{
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
break;
case 6:
break;
case 0:
printf("退出通讯录\n");
break;
default:
printf("选择错误,请重新选择\n");
}
} while (input);
return 0;
}
1.3 创建并初始化结构体对象
void InitContact(Contact* pc)
{
// 对pc指针进行断言
assert(pc);
// 将通讯录中的人数设置成0
pc->count = 0;
// 将通讯录的数据全部置成0
memset(pc->data, 0, sizeof(pc->data));
}
memset函数 将pc->data个字节的空间全部用0覆盖,赋初值
接下来就是各个功能模块的实现
1.4 模块化划分(核心)
1.4.1 AddContact
case 1:
AddContact(&con);
break;
case 1: 进行Add操作
// 往通讯录中增加
void AddContact(Contact* pc)
{
assert(pc);
if (pc->count == DATA_MAX)
{
printf("通讯录人数已满,无法添加\n");
return;
}
printf("当前人数:%d\n", pc->count);
// 往下标为count的位置添加信息
printf("请输入添加对象的姓名:");
scanf("%s", pc->data[pc->count].name);
printf("请输入添加对象的年龄:");
// scanf函数接收的是地址,所以要对age值取地址
// 其他元素都是数组,数组名就是首元素地址
scanf("%d", &(pc->data[pc->count].age));
printf("请输入添加对象的性别:");
scanf("%s", pc->data[pc->count].sex);
printf("请输入添加对象的电话:");
scanf("%s", pc->data[pc->count].tele);
printf("请输入添加对象的地址:");
scanf("%s", pc->data[pc->count].addr);
printf("添加成功\n");
// 人数++ 确保下一次操作正确进行
pc->count++;
}
1.4.2 DelContact
case 2:
DelContact(&con);
break;
case 2: 进行删除操作
// 将函数设置成静态的,只允许在当前的.c文件中访问
static int Find_by_name(Contact* pc, char* name)
{
int i = 0;
// 遍历整个数组当中的名字
for (i = 0; i < pc->count; i++)
{
// 两字符串比较相等用strcmp函数
if (strcmp(pc->data[pc->count].name, name))
{
// 将元素的下标返回
return i;
}
}
//没找到 返回-1
return -1;
}
// 删除信息
void DelContact(Contact* pc)
{
// 要想删除先要找到对象是否在通讯录当中
char name[NAME_MAX] = { 0 };
printf("请输入要删除人的姓名:>");
scanf("%s", name);
//查找名字
//拿 ret 接收返回值
int ret = Find_by_name(pc, name);
if (ret == -1)
{
printf("要删除的对象不在通讯录中\n");
return;
}
else
{
//删除就是将后面的对象往前覆盖,count再--
// 这里ret小于pc—>count -1为了防止越界访问
// 返回下标的作用就体现出来了
for (ret; ret < (pc->count)-1; ret++)
{
pc->data[ret] = pc->data[ret + 1];
}
pc->count--;
printf("删除完毕\n");
}
}
1.4.3 SearchContact
case 3:
SearchContact(&con);
break;
case 3 :进行查找操作
void SearchContact(Contact* pc)
{
// 这里可以引用上述Find_by_name代码
char name[NAME_MAX] = { 0 };
printf("请输入要查找人的姓名:>");
scanf("%s", name);
int ret = Find_by_name(pc, name);
if (ret == -1)
{
printf("要查找的对象不在通讯录中\n");
return;
}
else
{
//查找到对象要将其信息打印出来
printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");
printf("%-20s\t%-5d\t%-5s\t%-12s\t%-30s\n", pc->data[ret].name,
pc->data[ret].age,
pc->data[ret].sex,
pc->data[ret].tele,
pc->data[ret].addr);
}
}
1.4.4 ModifyContact
case 4:
ModifyContact(&con);
break;
case 4:进行修改操作
void ModifyContact(Contact* pc)
{
//同样要先找到所要修改的对象
char name[NAME_MAX] = { 0 };
printf("请输入所要修改人的姓名:>");
scanf("%s", name);
int ret = Find_by_name(pc, name);
if (ret == -1)
{
printf("要修改的对象不在通讯录中\n");
return;
}
//怎么修改?
// 将下标为ret的元素重新输入一遍,即为修改
printf("请输入修改后的姓名:");
scanf("%s", pc->data[ret].name);
printf("请输入修改后的年龄:");
scanf("%d", &(pc->data[ret].age));
printf("请输入修改后的性别:");
scanf("%s", pc->data[ret].sex);
printf("请输入修改后的电话:");
scanf("%s", pc->data[ret].tele);
printf("请输入修改后的地址:");
scanf("%s", pc->data[ret].addr);
printf("修改成功\n");
}
1.4.5 ShowContact
case 5:
ShowContact(&con);
break;
case 5: 进行展示操作
void ShowContact(Contact* pc)
{
// 判断通讯录当中是否有对象
if (pc->count == DATA_MIN)
{
printf("通讯录中没有元素,无法显示\n");
return;
}
//有元素
int i = 0;
printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");
for (i = 0; i < pc->count; i++)
{
printf("%-20s\t%-5d\t%-5s\t%-12s\t%-30s\n", pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].tele,
pc->data[i].addr);
}
}
1.4.6 SortContact
case 6:
SortContact(&con);
break;
case 6 :进行排序操作
int cmp_by_name(const void *e1,const void *e2)
{
//比较字符串用strcmp
return strcmp(((Peo_info*)e1)->name, ((Peo_info*)e2)->name);
}
void SortContact(Contact* pc)
{
//对通讯录中的对象进行排序
// 这里采取对名字排序
// 快排
qsort(pc->data, pc->count, sizeof(Peo_info), cmp_by_name);
printf("排序成功\n");
}
使用快排,快排的参数 要比较的起始位置,比较的个数,所比较元素的大小,比较方法
模块划分实现完毕
具体操作的实现,到这里基本的静态版本通讯录就完成了
2.动态版本
gitee码仓:动态版本实现通讯录的具体代码
动态版本与静态版本最大的区别就是动态开辟内存(根据需要开辟内存空间)
既然选择动态开辟,那么struct Contact 里面就不能用数组来固定元素个数,而应该用指针的形式(再通过malloc开辟内存)
2.1 修改结构体成员
结构体类型进行了修改,那么通讯录的初始化也要进行相应的修改
2.2 修改初始化函数
// 动态版本
int InitContact(Contact* pc)
{
// 对pc指针进行断言
assert(pc);
// 将通讯录中的人数设置成0
pc->count = 0;
pc->data = (Peo_info*)calloc(3, sizeof(Peo_info));
// 如果没开辟成功返回错误码信息
if (pc->data == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
// 开辟成功
//容量设置成默认大小:当前为3
pc->capacity = DEFAULT_SZ;
//通常C语言中没有错误返回的是0,有错误返回1
return 0;
}
通过calloc开辟并初始化内存空间
同样的,Add函数涉及增容的概念,如果空间开辟的不够要进一步增加空间
2.3 修改Add函数
void CheckCapacity(Contact* pc)
{
//既然是动态开辟的就不存在人数满的情况
//if (pc->count == DATA_MAX)
//{
// printf("通讯录人数已满,无法添加\n");
// return;
//}
// 容量等于当前人数,需要扩容
if (pc->count == pc->capacity)
{
// 用realloc 函数调整空间大小,每次增长2个Peo_info的空间
// realloc 调整空间后同样需要指针接收 ptr
Peo_info* ptr = (Peo_info*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(Peo_info));
//增容失败,报错
if (ptr == NULL)
{
printf("AddContact::%s\n", strerror(errno));
}
else
{
// 接收ptr,指向增容后的空间
pc->data = ptr;
pc->capacity += INC_SZ;
printf("增容成功\n");
}
}
}
//动态版本
void AddContact(Contact* pc)
{
assert(pc);
// 将增容封装成函数
CheckCapacity(pc);
//....后续内容不变
2.4 修改退出选项
退出通讯录就需要free进行空间的释放,因为通讯录中的data都是通过malloc,realloc开辟得来的,需要释放
void DestroyContact(Contact* pc)
{
assert(pc);
//释放data空间
free(pc->data);
//设置成0 pc->data被free后成为野指针,置成NULL才安全
pc->data = NULL;
}
case 0:
DestroyContact(&con);
printf("退出通讯录\n");
break;
在case 0 中添加free释放空间,退出程序
动态的版本到这里也就实现完成了
3.文件版本
3.1 保存数据到文件
首先在退出通讯录的时候需要能实现将数据写入文件当中(存入磁盘当中,而不是只能存在于内存)
所以在case 0:DesroyContact之前加入一个保存模块SaveContact
case 0:
SaveContact(&con);
DestroyContact(&con);
printf("退出通讯录\n");
break;
Save函数的代码实现:
3.2 将数据读入通讯录中
存储在文件中的数据需要能展现出来,(在内存:控制台中可以看见数据)从磁盘中读入内存
在初始化InitContact中不仅要实现动态开辟内存,而且需要把之前存在磁盘文件中的数据存入通讯录中
CheckCapacity 检查空间是否足够(在动态版本中实现的检查空间判断增容函数)
到这里文件版本实现通讯录也就完成了
但是我们知道
相较于文件,数据库存储数据更具优势,当然这部分的知识还未深入学习…
那么一整个通讯录的实现到这也就完全实现喽!!!撒花✿✿ヽ(°▽°)ノ✿