前面我们学习了顺序表的实现,接下来我们一起来运用一下吧--用顺序表实现通讯录。
引入
思考:我们学习了静态顺序表和动态顺序表,我们该用哪一种来实现通讯录呢?首先,我们来复习一下,静态顺序表和动态顺序表有什么区别呢?
静态顺序表:
实现静态顺序表需要创建两个变量,第一个定长的数组用来存放数据;size用来记录有效数据的个数。
既然数组是定长的,那么静态顺序表就有一个致命的弱点,能够储存的数据有限,这个数组空间开辟大了会造成空间的浪费如果开辟小了,就可能造成数据丢失。
动态顺序表:
动态的顺序表的实现需要创建3个变量,指针arr记录开始的位置;size记录有效数据与的个数;capacity用来记录空间的容量。相比于静态顺序表。动态顺序表的内存是动态开辟的,更加灵活。
typedef struct SeqList
{
SLDataType* arr;
int size;//有效数据个数
int capacity;//空间大小
}SL;
接下来,我们分析要实现的项目--通讯录。通讯录用来存放联系人的信息,存放的数据量是不确定的,且可能需要不停的改变,用静态顺序表可能会导致空间浪费或者内存泄漏。所以我们选择动态顺序表来实现。
要实现的功能
在这里我们还是使用程序的模块化实现,通常会有三个文件,头文件,实现文件和测试文件。这里我们要用到前面顺序表提供的方法所以要包含顺序表的头文件和实现文件一共五个文件。
功能声明(头文件)
typedef struct personInfo
{
char name[NAME_MAX];
char gender[GENDER_MAX];
int age;
char tel[TEL_MAX];
char addr[ADDR_MAX];
}peoInfo;
顺序表中我们储存的数据是字符,在通讯录中我们要储存的数据是一个个结构体
#pragma once
#define NAME_MAX 20
#define GENDER_MAX 10
#define TEL_MAX 20
#define ADDR_MAX 100
//定义联系人数据 结构
//姓名 性别 年龄 电话 地址
typedef struct personInfo
{
char name[NAME_MAX];
char gender[GENDER_MAX];
int age;
char tel[TEL_MAX];
char addr[ADDR_MAX];
}peoInfo;
//要用到顺序表的相关方法,对通讯录的实际操作就是对顺序表进行操作
//给顺序表改个名字,叫通讯录
typedef struct SeqList Contact;
//通讯录相关的方法
//通讯录的初始化
void ContactInit(Contact* con);
//通讯录的销毁
void ContactDesTroy(Contact* con);
//通讯录添加数据
void ContactAdd(Contact* con);
//通讯录删除数据
void ContactDel(Contact* con);
//通讯录的修改
void ContactModify(Contact* con);
//通讯录的查找
void ContactFind(Contact* con);
//展示通讯录数据
void ContactShow(Contact* con);
到这里,我们程序的结果就有一个大概的轮廓了。接下来我们就可以一次实现各个功能了。
功能实现(实现文件)
初始化
首先是通讯录的初始化,在顺序表中有现成的方法,我们可以直接使用
//通讯录的初始化
void ContactInit(Contact* con)
{
//实际上要进行的是顺序表的初始化
SLInit(con);
//顺序表的初始化已经实现好了,直接调用
}
销毁
//通讯录的销毁
void ContactDesTroy(Contact* con)
{
SLDestory(con);
}
添加联系人
由于通讯录中的数据有很多,需要依次添加,根据头文件中声明,除了年龄其他的都是数组(数组名代表数组首元素的地址),而scanf是直接把内容保存到地址,所以我们使用取地址操作符来添加年龄。
//通讯录添加数据
void ContactAdd(Contact* con)
{
//获取用户输入的内容:姓名+性别+年龄+电话+地址
peoInfo info;
printf("请输入要添加的联系人的姓名:\n");
scanf("%s", info.name);
printf("请输入要添加的联系人的性别:\n");
scanf("%s", info.gender);
printf("请输入要添加的联系人的年龄:\n");
scanf("%d", &info.age);
printf("请输入要添加的联系人的电话:\n");
scanf("%s", info.tel);
printf("请输入要添加的联系人的地址:\n");
scanf("%s", info.addr);
//往通讯录中添加联系人数据
SLPushBack(con, info);
}
删除联系人
接下来,我们来实现删除联系人的数据这个功能,要删除联系人数据,首先我们数据要存在我们才能找到。我们可以写一个函数来判断是否找到了,如果FindByName()的返回值大于0,就找到了,反之,则没有找到。
int FindByName(Contact* con, char name[])
{
for (int i = 0; i < con->size; i++)
{
if (0 == strcmp(con->arr[i].name, name))
{
//找到了
return i;
}
}
//没有找到
return -1;
}
//通讯录删除数据
void ContactDel(Contact* con)
{
//要删除的数据必须要存在,才能执行删除操作
//查找
char name[NAME_MAX];
printf("请输入要删除联系人的姓名:\n");
scanf("%s", name);
int find = FindByName(con, name);
if (find < 0)
{
printf("要删除的联系人数据不存在!\n");
return;
}
//要删除的联系人数据存在
SLErase(con, find);
printf("删除成功\n");
}
展示通讯录
//展示通讯录数据
void ContactShow(Contact* con)
{
//表头:姓名 性别 年龄 电话 地址
printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "地址");
//遍历通讯录,按照格式打印每个联系人数据
for (int i = 0; i < con->size; i++)
{
printf("%3s %3s %3d %3s %3s\n",
con->arr[i].name,
con->arr[i].gender,
con->arr[i].age,
con->arr[i].tel,
con->arr[i].addr
);
}
}
通讯录的修改
要修改首先要找到,用FindByName找到这个联系人。再借用添加联系人的方法来实现修改。
//通讯录的修改
void ContactModify(Contact* con)
{
//要修改的联系人数据要存在
char name[NAME_MAX];
printf("请输入要修改的用户姓名:\n");
scanf("%s", name);
int find = FindByName(con, name);
if (find < 0)
{
printf("要修改的联系人数据不存在!\n");
return;
}
//直接修改
printf("请输入新的姓名:\n");
scanf("%s", con->arr[find].name);
printf("请输入新的性别:\n");
scanf("%s", con->arr[find].gender);
printf("请输入新的年龄:\n");
scanf("%d", &con->arr[find].age);
printf("请输入新的的电话:\n");
scanf("%s", con->arr[find].tel);
printf("请输入新的地址:\n");
scanf("%s", con->arr[find].addr);
printf("修改成功\n");
}
联系人的查找
找到这个联系人,再把这个联系人数据打印出来
//通讯录的查找
void ContactFind(Contact* con)
{
char name[NAME_MAX];
printf("请输入要查找的联系人姓名\n");
scanf("%s", name);
int find = FindByName(con, name);
if (find < 0)
{
printf("要查找的联系人数据不存在!\n");
return;
}
printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "地址");
printf("%3s %3s %3d %3s %3s\n",
con->arr[find].name,
con->arr[find].gender,
con->arr[find].age,
con->arr[find].tel,
con->arr[find].addr
);
}
注意事项
头文件不能交叉包含
前置声明
在顺序表的头文件中,我们需要用到Contact.h的声明,但是我们在Contact.h也需要用到Seqlist.h中定义的struct SeqList,不能交叉包含头文件,我们又要用到,这时我们使用前置声明。
测试
我们写好一个方法就应该测试一个方法,防止问题到后边越堆越多。