介绍:
在前面我们提到过,将顺序表中储存数据的类型改为结构体类型,整个顺序表就类似于我们日常生活中的通讯录,也就是说,通讯录可以同过顺序表实现;
而在我们学习完链表之后,我们也知道,顺序表和链表同属于线性表,他们所存储的数据在逻辑上都是连续的,那这个时候我们不禁发出疑问,链表能实现通讯录吗?
答案当然是肯定的,当我们将链表中每个结点中所存储的数据类型改为结构体类型之后,那链表的每个结点就相当于我们通讯录中每一个联系人,那整个链表,也就是我们日常生活中使用的通讯录,今天这篇文章,就来带大家全方位用链表来实现通讯录;
1.准备工作:
1.1.链表中一些对应功能的实现:
实现链表通讯录的增删查改也会使用到和链表增删查改有关的功能,如下:
//单链表的一个结点
typedef struct SListNode
{
SLDataType data;
struct SListNode* next;
}SLNode;
//创建单链表的一个结点
SLNode* BuySListNode(SLDataType x);
//单链表尾插数据
void SListPushBack(SLNode** pphead, SLDataType x);
//删除pos结点
void SListErase(SLNode** pphead, SLNode* pos);
//销毁通讯录数据
void DestroyContact(contact** con);
相信大家对单链表的增删查改早已熟记于心,所以将这部分功能实现的代码直接给出来供给参考,若是这方面知识还稍有欠缺和不足,一定要加紧练习,熟悉单链表的功能:
SLNode* BuySListNode(SLDataType x)
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
if (newnode == NULL)
{
perror("malloc");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SListPushBack(SLNode** pphead, SLDataType x)
{
assert(pphead);
SLNode* newnode = BuySListNode(x);
if (*pphead == NULL)
{
//链表为空
*pphead = newnode;
}
else
{
SLNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
void SListErase(SLNode** pphead, SLNode* pos)
{
assert(pphead && *pphead);
assert(pos);
if (pos == *pphead)
{
SLNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
else
{
SLNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
void SListDestory(SLNode** pphead)
{
SLNode* cur = *pphead;
while (cur)
{
SLNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
1.2.通讯录联系人的定义:
既然要将链表结点中存储的数据的类型改为结构体类型,那么我们当然要把这个结构体给定义出来,这个结构体中需包含联系人的信息,如姓名、性别、年龄、电话号码、家庭地址。
#define NAME_MAX 20
#define GENDER_MAX 10
#define TELE_MAX 20
#define ADDR_MAX 100
typedef struct PersonInform
{
char name[NAME_MAX];
char gender[GENDER_MAX];
int age;
char tele[TELE_MAX];
char address[ADDR_MAX];
}PI;
然后将链表结点中存储数据的类型改为PI这一结构体类型;
typedef struct PersonInform SLDataType;
1.3.名称转换:
既然是通讯录,即使其本质上也是链表实现增删查改,但是为了我们代码的可读性和更明确的目的性,我们在特定的名称上需要进行转换:
链表的每一个结点SLNode应改为contact(联系人);
struct SListNode;
typedef struct SListNode contact;
但这里我们要注意,我们在实现通讯录的时候,通讯录的功能当然要单独放置在一个头文件中,我们记为Contact.h,Contact.h头文件最终是要包含在SList.h链表头文件中的,因此所有通讯录的准备工作的实现都是在链表头文件中的内容之前的,所以我们在改链表结点的名称时,应该提前声明一下;
接下来正式进入我们通讯录的实现
2.通讯录的实现:
2.1.通讯录添加联系人:
//添加通讯录数据
void AddContact(contact** con);
在实现链表时,我们都是从定义链表的一个结点的指针开始,链表通讯录也是一样,通讯录中可能原本就没有存储任何联系人,即contact* con=NULL,那这个时候我们就要改变他的指向,让他指向我们所要创建的第一个联系人,所以通讯录的添加联系人也需要传入二级指针;
在该函数中,我们需要先定义出这个联系人,并给出其所有数据,再按照链表头插或尾插的方式插入进去:
void AddContact(contact** con)
{
PI inform = { 0 };
printf("请输入要添加的联系人的姓名:>\n");
scanf("%s", inform.name);
printf("请输入要添加的联系人的性别:>\n");
scanf("%s", inform.gender);
printf("请输入要添加的联系人的年龄:>\n");
scanf("%d", &inform.age);
printf("请输入要添加的联系人的电话号码:>\n");
scanf("%s", inform.tele);
printf("请输入要添加的联系人的家庭住址:>\n");
scanf("%s", inform.address);
SListPushBack(con, inform);
}
2.2.通讯录查找联系人:
//查找通讯录数据
void FindContact(contact* con);
这个功能是为了通过姓名或者电话号码查找到某一联系人,并将其数据全部展示出来:
在实现这个函数之前,我们需要先实现以下两个函数——通过姓名和电话号码查找联系人
//通过电话号码查找联系人,找到后返回该联系人对应的结点
contact* FindByTele(contact* con, char* tele);
//通过姓名查找联系人
contact* FindByName(contact* con, char* name);
查找到该联系人,返回对应的结点,然后我们对这个返回的结点就能加以运用;
这两个函数的实现极其相似,因此在实现这两个函数的过程中,我们需要使用到strcmp函数,不要忘了包含string.h头文件;
2.2.1.FindByTele实现:
contact* FindByTele(contact* con, char* tele)
{
assert(con);
contact* cur = con;
while (cur)
{
if (strcmp(cur->data.tele, tele) == 0)
return cur;
cur = cur->next;
}
return NULL;//没找到
}
tele为我们要查找的电话号码,找不到则返回空。
2.2.2.FindByName实现:
contact* FindByName(contact* con, char* name)
{
assert(con);
contact* cur = con;
while (cur)
{
if (strcmp(cur->data.name, name) == 0)
return cur;
cur = cur->next;
}
return NULL;//没找到
}
name为我们要查找的联系人姓名,找不到则返回空;
2.2.3.FindContact实现:
那在实现完这两个函数之后,我们就能直接实现FindContact函数,在实现的时候我们应设置一个类似于菜单的东西,来确定是通过电话号码还是姓名来删除联系人,该函数的实现非常简单,这里直接将代码给出:
void FindContact(contact* con)
{
assert(con);
//有两种查找方式,可通过姓名和电话号码查找,找到后打印出该联系人的全部信息
int choice = 0;
printf("********* 请选择查找方式:**********\n");
printf("************* 1.tele **************\n");
printf("************* 2.name **************\n");
printf("************* 0.退出 *************\n");
printf("请选择:>");
contact* find = NULL;
do
{
scanf("%d", &choice);
switch (choice)
{
case 1:
printf("请输入要查找的电话号码:\n");
char tele[TELE_MAX] = "happy";
scanf("%s", tele);
find = FindByTele(con, tele);
break;
case 2:
printf("请输入要查找的姓名:\n");
char name[NAME_MAX] = "happy";
scanf("%s", name);
find = FindByName(con, name);
break;
case 0:
printf("已退出\n");
break;
default:
printf("选择错误,重新选择:\n");
break;
}
} while (choice != 0 && choice != 1 && choice != 2);
//选择的不是 0、1、2 就会继续调用
if (find == NULL)
{
printf("查无此人:\n");
}
else
{
printf("姓名: %s\n", find->data.name);
printf("性别: %s\n", find->data.gender);
printf("年龄: %d\n", find->data.age);
printf("电话: %s\n", find->data.tele);
printf("住址: %s\n", find->data.address);
}
}
2.3.通讯录删除联系人:
//删除通讯录数据
void DelContact(contact** con);
对于该功能的实现,我们要选择通过姓名或者电话号码来删除联系人,这里我们以通过姓名来删除联系人做示范,当然大家也能有自己的想法,代码的实现路径往往不是唯一的;
首先通过FindByName或者FindByTele函数来找到我们需要删除的联系人,结合SListErase来删除这个联系人:
void DelContact(contact** con)
{
assert(*con);
printf("请输入要删除的对应联系人的姓名:>\n");
char name[NAME_MAX] = "happy";
scanf("%s", name);
contact* find = FindByName(*con, name);
if (find != NULL)
SListErase(con, find);
else
printf("查无此人\n");
}
2.4.通讯录修改联系人:
//修改通讯录数据
void ModifyContact(contact* con);
该功能的实现和删除联系人非常类似,也是通过FindByName或者FindByTele找到对应我们需要删除的联系人,存放于find中,然后通过find找到该联系人,对其数据进行修改,我们可以发现,这只不过对find的操作和DelContact函数有所不同而已:
void ModifyContact(contact* con)
{
assert(con);
printf("请输入要修改的对应联系人的姓名:>\n");
char name[NAME_MAX] = "happy";
scanf("%s", name);
contact* find = FindByName(con, name);
if (find != NULL)
{
printf("请输入新联系人的姓名:\n");
scanf("%s", find->data.name);
printf("请输入新联系人的性别:\n");
scanf("%s", find->data.gender);
printf("请输入新联系人的年龄:\n");
scanf("%d", &find->data.age);
printf("请输入新联系人的电话:\n");
scanf("%s", find->data.tele);
printf("请输入新联系人的住址:\n");
scanf("%s", find->data.address);
}
else
printf("查无此人\n");
}
2.5.展示通讯录和摧毁通讯录:
//展示通讯录数据
void ShowContact(contact* con);
//销毁通讯录数据
void DestroyContact(contact** con);
这两个函数的实现就非常直接了,尤其是摧毁通讯录,和链表的摧毁一模一样,这里给出代码:
void ShowContact(contact* con)
{
assert(con);
contact* cur = con;
printf("| 姓名 | 性别 | 年龄 | 电话 | 住址 |\n");
while (cur)
{
printf("| %s | %s | %d | %s | %s |\n", cur->data.name, cur->data.gender, cur->data.age
, cur->data.tele, cur->data.address);
cur = cur->next;
}
}
void DestroyContact(contact** con)
{
assert(con);
SListDestory(con);
}
当然了,我们也可以通过自己的发挥来让其展示的画面更加美观;
3.整体功能展示:
为了展示这个通讯录的功能,我们可以定义一个枚举变量和一个菜单,来增加我们代码的可读性:
enum Contact
{
EXIT,
ADD,
DEL,
FIND,
MODIFY,
SHOW
};
void menu()
{
printf("***********---欢迎浏览通讯录---***********\n");
printf("******- 1.Add -*********- 2.Del***********\n");
printf("******- 3.Find -********- 4.Modify -*******\n");
printf("******- 5.Show -********- 0.Exit -*****\n");
printf("请选择:>");
}
代码如下:
int main()
{
//TestContact();
contact* con = NULL;
int choice = 0;
do
{
menu();
scanf("%d", &choice);
switch (choice)
{
case ADD:
AddContact(&con);
break;
case DEL:
DelContact(&con);
break;
case FIND:
FindContact(con);
break;
case MODIFY:
ModifyContact(con);
break;
case SHOW:
ShowContact(con);
break;
case EXIT:
printf("已退出\n");
break;
default:
printf("选择错误,重新选择:\n");
break;
}
} while (choice);
DestroyContact(&con);
return 0;
}
4.展示结果:
5.整体代码展示:
到这里我们链表实现通讯录就已经全部结束了,和顺序表其实有很多地方时类似的,如果我们1能熟练的掌握通讯录的实现,相信我们对顺序表和链表的掌握已经非常熟练了,这里将实现这个通讯录的代码全部给出,方便大家自己实现的时候进行参考:
5.1.Contact.h头文件部分:
#pragma once
#include <string.h>
#define NAME_MAX 20
#define GENDER_MAX 10
#define TELE_MAX 20
#define ADDR_MAX 100
typedef struct PersonInform
{
char name[NAME_MAX];
char gender[GENDER_MAX];
int age;
char tele[TELE_MAX];
char address[ADDR_MAX];
}PI;
struct SListNode;
typedef struct SListNode contact;
//添加通讯录数据
void AddContact(contact** con);
//通过电话号码查找联系人,找到后返回该联系人对应的结点
contact* FindByTele(contact* con, char* tele);
//通过姓名查找联系人
contact* FindByName(contact* con, char* name);
//删除通讯录数据
void DelContact(contact** con);
//展示通讯录数据
void ShowContact(contact* con);
//查找通讯录数据
void FindContact(contact* con);
//修改通讯录数据
void ModifyContact(contact* con);
//销毁通讯录数据
void DestroyContact(contact** con);
5.2.Contact.c功能实现部分:
#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
void AddContact(contact** con)
{
PI inform = { 0 };
printf("请输入要添加的联系人的姓名:>\n");
scanf("%s", inform.name);
printf("请输入要添加的联系人的性别:>\n");
scanf("%s", inform.gender);
printf("请输入要添加的联系人的年龄:>\n");
scanf("%d", &inform.age);
printf("请输入要添加的联系人的电话号码:>\n");
scanf("%s", inform.tele);
printf("请输入要添加的联系人的家庭住址:>\n");
scanf("%s", inform.address);
SListPushBack(con, inform);
}
contact* FindByTele(contact* con, char* tele)
{
assert(con);
contact* cur = con;
while (cur)
{
if (strcmp(cur->data.tele, tele) == 0)
return cur;
cur = cur->next;
}
return NULL;//没找到
}
contact* FindByName(contact* con, char* name)
{
assert(con);
contact* cur = con;
while (cur)
{
if (strcmp(cur->data.name, name) == 0)
return cur;
cur = cur->next;
}
return NULL;//没找到
}
void FindContact(contact* con)
{
assert(con);
//有两种查找方式,可通过姓名和电话号码查找,找到后打印出该联系人的全部信息
int choice = 0;
printf("********* 请选择查找方式:**********\n");
printf("************* 1.tele **************\n");
printf("************* 2.name **************\n");
printf("************* 0.退出 *************\n");
printf("请选择:>");
contact* find = NULL;
do
{
scanf("%d", &choice);
switch (choice)
{
case 1:
printf("请输入要查找的电话号码:\n");
char tele[TELE_MAX] = "happy";
scanf("%s", tele);
find = FindByTele(con, tele);
break;
case 2:
printf("请输入要查找的姓名:\n");
char name[NAME_MAX] = "happy";
scanf("%s", name);
find = FindByName(con, name);
break;
case 0:
printf("已退出\n");
break;
default:
printf("选择错误,重新选择:\n");
break;
}
} while (choice != 0 && choice != 1 && choice != 2);
//选择的不是 0、1、2 就会继续调用
if (find == NULL)
{
printf("查无此人:\n");
}
else
{
printf("姓名: %s\n", find->data.name);
printf("性别: %s\n", find->data.gender);
printf("年龄: %d\n", find->data.age);
printf("电话: %s\n", find->data.tele);
printf("住址: %s\n", find->data.address);
}
}
void DelContact(contact** con)
{
assert(*con);
printf("请输入要删除的对应联系人的姓名:>\n");
char name[NAME_MAX] = "happy";
scanf("%s", name);
contact* find = FindByName(*con, name);
if (find != NULL)
SListErase(con, find);
else
printf("查无此人\n");
}
void ShowContact(contact* con)
{
assert(con);
contact* cur = con;
printf("| 姓名 | 性别 | 年龄 | 电话 | 住址 |\n");
while (cur)
{
printf("| %s | %s | %d | %s | %s |\n", cur->data.name, cur->data.gender, cur->data.age
, cur->data.tele, cur->data.address);
cur = cur->next;
}
}
void ModifyContact(contact* con)
{
assert(con);
printf("请输入要修改的对应联系人的姓名:>\n");
char name[NAME_MAX] = "happy";
scanf("%s", name);
contact* find = FindByName(con, name);
if (find != NULL)
{
printf("请输入新联系人的姓名:\n");
scanf("%s", find->data.name);
printf("请输入新联系人的性别:\n");
scanf("%s", find->data.gender);
printf("请输入新联系人的年龄:\n");
scanf("%d", &find->data.age);
printf("请输入新联系人的电话:\n");
scanf("%s", find->data.tele);
printf("请输入新联系人的住址:\n");
scanf("%s", find->data.address);
}
else
printf("查无此人\n");
}
void DestroyContact(contact** con)
{
assert(con);
SListDestory(con);
}
5.3.测试及实验部分:
#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
void TestContact()
{
contact* con = NULL;
AddContact(&con);
AddContact(&con);
FindContact(con);
}
enum Contact
{
EXIT,
ADD,
DEL,
FIND,
MODIFY,
SHOW
};
void menu()
{
printf("***********---欢迎浏览通讯录---***********\n");
printf("******- 1.Add -*********- 2.Del***********\n");
printf("******- 3.Find -********- 4.Modify -*******\n");
printf("******- 5.Show -********- 0.Exit -*****\n");
printf("请选择:>");
}
int main()
{
//TestContact();
contact* con = NULL;
int choice = 0;
do
{
menu();
scanf("%d", &choice);
switch (choice)
{
case ADD:
AddContact(&con);
break;
case DEL:
DelContact(&con);
break;
case FIND:
FindContact(con);
break;
case MODIFY:
ModifyContact(con);
break;
case SHOW:
ShowContact(con);
break;
case EXIT:
printf("已退出\n");
break;
default:
printf("选择错误,重新选择:\n");
break;
}
} while (choice);
DestroyContact(&con);
return 0;
}