一.题目要求
今天来和大家分享一个简易通讯录(C语言实现)
首先要介绍一下通讯录的基本功能
- 添加联系人信息
- 删除指定联系人信息
- 查找指定联系人信息
- 修改指定联系人信息
- 显示所有联系人信息
二.思路分析
1. 首先需要定义通讯录的数据结构,可以使用结构体来表示每个联系人的信息,包括姓名、电话号码、地址等。 2. 接着需要定义一个数组来存储所有联系人的信息,数组的大小可以根据实际需求进行调整。 3. 编写程序菜单,包括添加联系人、删除联系人、查找联系人、显示所有联系人等功能。 4. 在添加联系人功能中,需要让用户输入联系人的信息,并将其存储到数组中。 5. 在删除联系人功能中,需要让用户输入要删除的联系人姓名,并在数组中查找并删除该联系人的信息。 6. 在查找联系人功能中,需要让用户输入要查找的联系人姓名,并在数组中查找并显示该联系人的信息。 7. 在显示所有联系人功能中,需要遍历数组并逐个显示每个联系人的信息。 8. 最后,可以使用文件读写功能将通讯录数据保存到文件中,以便下次启动程序时可以读取之前保存的数据。
本次实验是上次实验的补充,可以参考。
三.各部分功能实现
1.定义通讯录结构
- 在单链表中,假设每个结点的类型用LinkNode表示,它应包括存储元素的数据域,这里用data表示,其类型用通用类型标识符ElemType 表示,还包括存储后继结点位置的指针域,这里用next表示。LinkNode 类型的声明如下:
//定义通讯录结构表
typedef struct {
char name[20];
char tel[15];
char place[20];
} Elemtype;
//声明链表
typedef struct LNode
{
Elemtype data;
struct LNode *next;
}LinkNode;
/*
本次实验是用单链表来实现通讯录的建立
这里我们的基本思路是先初始化一个空的线性表,然后通过读入数组的信息来建立初始的通讯录。
读入数组的信息有两种方法读取插入:头插法和尾插法。
头插法:将读取的数组元素存放在新节点的数据域中,然后将其插入到当前链表的表头(头节点之后)
尾插法:将读取的数组元素存放在新节点的数据域中,然后将其插入到当前链表的表尾(尾节点之后)
这里分别用两种方法进行开始信息的插入。
*/
2.初始化以及销毁线性表
- 初始化:建立一个空的 单链表
- 销毁线性表:该运算释放单链表L占用的内存空间,即逐一释放全部结点的空间。其过程是让pre、p 指向两个相邻的结点(初始时pre指向头结点,p指向首结点)。当p不为空时循环:释放结点pre,然后pre、p同步后移一个结点。循环结束后,pre指向尾结点,再将其释放。算法如下:
//初始化线性表,即建立一个空的链表
void InitList(LinkNode *&L)
{
L=(LinkNode * )malloc(sizeof(LinkNode));
L->next=NULL; //创建头节点,其next域置为空
}
/*
头插法算法思路:
1.首先建立一个头节点L(已存在);
2.读入数组
3.建立一个新节点p
4.将数组元素依次赋值给p的数据域
5.修改指针
6.重复3~5,直到结束
*/
//头插法建立单链表
void CreateListF(LinkNode *&L,Elemtype a[],int n)
{
LinkNode *s;
//头节点已存在,不再在这里建立了
for(int i=0;i<n;i++)
{
s=(LinkNode * )malloc(sizeof(LinkNode)); //创建数据新节点
s->data=a[i]; //将数组元素赋值给s的数据域
s->next=L->next; //将s放在原来L节点之后
L->next=s;
}
}
//尾插法建立单链表
void CreateListR(LinkNode *&L,Elemtype a[],int n)
{
LinkNode *s,*r;
//头节点已存在,不再在这里建立了
r=L; //r始终指向尾节点,但初始时指向头节点(初始时头节点即为尾节点)
for(int i=0;i<n;i++)
{
s=(LinkNode * )malloc(sizeof(LinkNode)); //创建数据新节点
s->data=a[i]; //将数组元素赋值给新节点s的数据域
r->next=s; //将s放在原来尾指针r的后面
r=s;
}
r->next=NULL; //插入完成后,尾节点的next域为空
}
3.插入数据元素
- 该运算的实现过程是先在单链表L中找到第i-1个结点,由p指向它。若存在这样的
结点,将值为e的结点(s指向它)插人到p所指结点的后面。算法如下:
//插入数据元素
bool ListInsert(LinkNode *&L,int i,Elemtype e)
{
/*在链表L的第i个位置上插入新元素e*/
int j=0;
LinkNode *p=L,*s; //p开始指向头节点,s为存放数据新节点
if(i<=0) //位置不对就报错
return false;
while(j<i-1 && p!=NULL) //定位,使p指向第i-1个节点
{
j++;
p=p->next;
}
if(p==NULL) //如果没找到第i-1个节点就报错
return false;
else //成功定位后,执行下面操作
{
s=(LinkNode * )malloc(sizeof(LinkNode)); //创建新节点s,其数据域置为e
s->data=e;
s->next=p->next; //创建的新节点s放在节点p之后
p->next=s;
return true;
}
}
4.删除数据元素
- 该运算的实现过程是先在单链表L中找到第i-1个结点,由p指向它。若存在这样结点,且也存在后继结点(由q指向它),则删除q所指的结点,返回true;否则返回false表示参数i错误。算法如下:
//删除数据元素
bool ListDelate (LinkNode *&L, int i, Elemtype &e)
{
/*删除链表中的第i个元素,其值为e*/
int j=0;
LinkNode *p=L,*q; //p开始指向头节点,q为要删除的节点
if(i<0) //位置不对就报错
return false;
while(j<i-1 && p!=NULL) //定位,使p指向第i-1个节点
{
j++;
p=p->next;
}
if(p==NULL) //如果没找到第i-1个节点就报错
return false;
else //成功定位后,执行下面操作
{
q=p->next; //q指向第i个节点
if(q==NULL) //如果不存在要删除的节点就报错
return false;
e=q->data; //e存放要删除节点的数据域
p->next=q->next; //从单链表中删除q节点
free(q); //释放q节点
return true;
}
}
5.输出线性表
- 该运算逐一扫描单链表L的每个数据结点,并显示各结点的data域值。
//输出线性表
void DispList(LinkNode *L)
{
LinkNode *p=L->next; //p指向首节点
printf("------------通讯录-------------\n");
while(p!=NULL) //p不为空就输出p节点的data域
{
printf("%s %s %s\n",p->data.name,p->data.tel,p->data.place);
p=p->next; //p移向下一位节点
}
printf("-------------------------------\n");
}
6.求通讯录表长度
- 该运算返回单链表L中数据结点的个数。由于单链表没有存放数据结点个数的信息,
需要通过遍历来统计。其过程是让p指向头结点,n用来累计数据结点个数(初始值为0),
当p不为空时循环: n增1,p指向下一个结点。循环结束后返回n。算法如下:
//求通讯录表长度
int ListLength(LinkNode *L)
{
int count=0; //统计长度,开始为0
LinkNode *p=L; //p指向头节点,此时计数器为0
while(p->next!=NULL) //开始计数,p移动一次计数器加1
{
count++;
p=p->next;
}
return count; //循环结束,p指向尾节点,count为节点个数
}
7.获取数据元素
- 该运算在单链表L中从头开始找到第i个结点,若存在第i个数据结点,则将其date值赋给变量e。其过程是让p指向头结点,j用来累计遍历过的数据结点个数(初始值为0),当j<i且p不为空时循环;j增1,p指向下一个结点。循环结束后有两种情况,若p为空,表示单链表 L中没有第i 个数据结点(参数i错误),返 回false; 否则找到第i个数据与点,提取它的值并返回true.。算法如下:
//获取线性表中某个位置数据元素
bool GetElem(LinkNode *L,int i,Elemtype &e)
{
int j=0;
LinkNode *p=L; //p开始指向头节点
if(i<0) //位置不对就报错
return false;
while(j<i && p!=NULL) //定位,使p指向第i个节点
{
j++;
p=p->next;
}
if(p==NULL) //如果没找到第i个节点就报错
return false;
else //成功定位后,执行下面操作
{
e=p->data; //e存放第i个节点的数据域
return true;
}
}
8.元素查找
- 该运算在单链表工中从头开始找第一个值域与e相等的结点,若存在这样的结点,则返
回逻辑序号,否则返回0。算法如下:
//元素查找(通讯录姓名查找)
int LocateElem(LinkNode *L, Elemtype e)
{
/*从头开始查找第一个值域与e相等的节点,返回逻辑序号(根据e找i)*/
int i=1;
LinkNode *p=L->next; //p指向首节点,首节点的序号为1
while(p!=NULL && strcmp(p->data.name,e.name)) //循环查找姓名相同的节点
{
p=p->next;
i++;
}
if(p==NULL) //不存在这样的节点返回0
return 0;
else
printf("%s %s %s\n",p->data.name,p->data.tel,p->data.place);
return i;
}
四. 完整代码
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
//定义通讯录结构表
typedef struct {
char name[20];
char tel[15];
char place[20];
} Elemtype;
//声明链表
typedef struct LNode
{
Elemtype data;
struct LNode *next;
}LinkNode;
/*
本次实验是用单链表来实现通讯录的建立
这里我们的基本思路是先初始化一个空的线性表,然后通过读入数组的信息来建立初始的通讯录。
读入数组的信息有两种方法读取插入:头插法和尾插法。
头插法:将读取的数组元素存放在新节点的数据域中,然后将其插入到当前链表的表头(头节点之后)
尾插法:将读取的数组元素存放在新节点的数据域中,然后将其插入到当前链表的表尾(尾节点之后)
这里分别用两种方法进行开始信息的插入。
*/
//初始化线性表,即建立一个空的链表
void InitList(LinkNode *&L)
{
L=(LinkNode * )malloc(sizeof(LinkNode));
L->next=NULL; //创建头节点,其next域置为空
}
/*
头插法算法思路:
1.首先建立一个头节点L(已存在);
2.读入数组
3.建立一个新节点p
4.将数组元素依次赋值给p的数据域
5.修改指针
6.重复3~5,直到结束
*/
//头插法建立单链表
void CreateListF(LinkNode *&L,Elemtype a[],int n)
{
LinkNode *s;
//头节点已存在,不再在这里建立了
for(int i=0;i<n;i++)
{
s=(LinkNode * )malloc(sizeof(LinkNode)); //创建数据新节点
s->data=a[i]; //将数组元素赋值给s的数据域
s->next=L->next; //将s放在原来L节点之后
L->next=s;
}
}
//尾插法建立单链表
void CreateListR(LinkNode *&L,Elemtype a[],int n)
{
LinkNode *s,*r;
//头节点已存在,不再在这里建立了
r=L; //r始终指向尾节点,但初始时指向头节点(初始时头节点即为尾节点)
for(int i=0;i<n;i++)
{
s=(LinkNode * )malloc(sizeof(LinkNode)); //创建数据新节点
s->data=a[i]; //将数组元素赋值给新节点s的数据域
r->next=s; //将s放在原来尾指针r的后面
r=s;
}
r->next=NULL; //插入完成后,尾节点的next域为空
}
//插入数据元素
bool ListInsert(LinkNode *&L,int i,Elemtype e)
{
/*在链表L的第i个位置上插入新元素e*/
int j=0;
LinkNode *p=L,*s; //p开始指向头节点,s为存放数据新节点
if(i<=0) //位置不对就报错
return false;
while(j<i-1 && p!=NULL) //定位,使p指向第i-1个节点
{
j++;
p=p->next;
}
if(p==NULL) //如果没找到第i-1个节点就报错
return false;
else //成功定位后,执行下面操作
{
s=(LinkNode * )malloc(sizeof(LinkNode)); //创建新节点s,其数据域置为e
s->data=e;
s->next=p->next; //创建的新节点s放在节点p之后
p->next=s;
return true;
}
}
//删除数据元素
bool ListDelate (LinkNode *&L, int i, Elemtype &e)
{
/*删除链表中的第i个元素,其值为e*/
int j=0;
LinkNode *p=L,*q; //p开始指向头节点,q为要删除的节点
if(i<0) //位置不对就报错
return false;
while(j<i-1 && p!=NULL) //定位,使p指向第i-1个节点
{
j++;
p=p->next;
}
if(p==NULL) //如果没找到第i-1个节点就报错
return false;
else //成功定位后,执行下面操作
{
q=p->next; //q指向第i个节点
if(q==NULL) //如果不存在要删除的节点就报错
return false;
e=q->data; //e存放要删除节点的数据域
p->next=q->next; //从单链表中删除q节点
free(q); //释放q节点
return true;
}
}
//输出线性表
void DispList(LinkNode *L)
{
LinkNode *p=L->next; //p指向首节点
printf("------------通讯录-------------\n");
while(p!=NULL) //p不为空就输出p节点的data域
{
printf("%s %s %s\n",p->data.name,p->data.tel,p->data.place);
p=p->next; //p移向下一位节点
}
printf("-------------------------------\n");
}
//求通讯录表长度
int ListLength(LinkNode *L)
{
int count=0; //统计长度,开始为0
LinkNode *p=L; //p指向头节点,此时计数器为0
while(p->next!=NULL) //开始计数,p移动一次计数器加1
{
count++;
p=p->next;
}
return count; //循环结束,p指向尾节点,count为节点个数
}
//获取线性表中某个位置数据元素
bool GetElem(LinkNode *L,int i,Elemtype &e)
{
int j=0;
LinkNode *p=L; //p开始指向头节点
if(i<0) //位置不对就报错
return false;
while(j<i && p!=NULL) //定位,使p指向第i个节点
{
j++;
p=p->next;
}
if(p==NULL) //如果没找到第i个节点就报错
return false;
else //成功定位后,执行下面操作
{
e=p->data; //e存放第i个节点的数据域
return true;
}
}
//元素查找(通讯录姓名查找)
int LocateElem(LinkNode *L, Elemtype e)
{
/*从头开始查找第一个值域与e相等的节点,返回逻辑序号(根据e找i)*/
int i=1;
LinkNode *p=L->next; //p指向首节点,首节点的序号为1
while(p!=NULL && strcmp(p->data.name,e.name)) //循环查找姓名相同的节点
{
p=p->next;
i++;
}
if(p==NULL) //不存在这样的节点返回0
return 0;
else
printf("%s %s %s\n",p->data.name,p->data.tel,p->data.place);
return i;
}
//判断通讯录为空
bool ListEmpty(LinkNode *L)
{
return (L->next==NULL);
}
//菜单实现
void menu() {
printf(" -------------------------------\n");
printf(" 通讯录的应用:\n");
printf(" -------------------------------\n");
printf(" 1.建立(初始化)通讯录\n");
printf(" 2.显示联系人信息\n");
printf(" 3.增加联系人信息\n");
printf(" 4.删除联系人信息\n");
printf(" 5.查找联系人信息\n");
printf(" 6.退出程序!!!\n");
printf(" -------------------------------\n");
}
int main()
{
LinkNode *L;
int flag = 1; //定义循环体条件
int i, j; //存放用户输入的选项
Elemtype a[4] = {"张三", "15671580583", "北京","李四", "13387592396","上海","王五", "15994272725","武汉", "赵六", "15972200598","昆明"};
Elemtype e;
menu();
printf("初始化顺序表并用头插法插入开始元素:\n");
InitList(L); //这时是一个空表,接下来通过头插法创建线性表完成初始化
CreateListF(L,a,4); //采用头插法建立完成
DispList(L); //展示初始化成果
while(flag==1)
{
printf("请输入你的选择:\n");
scanf("%d", &j);
switch(j)
{
case 1:
printf("已经完成初始化\n");
break;
case 2:
DispList(L);
break;
case 3:
printf("请输入联系人姓名、电话与地址:");
scanf("%s %s %s", e.name, e.tel,e.place);
printf("请输入插入数据的位置:");
scanf("%d", &i);
printf("\n");
ListInsert(L,i,e);
break;
case 4:
printf("请输入删除数据的位置:");
scanf("%d", &i);
ListDelate(L, i, e);
break;
case 5:
printf("请输入联系人姓名:");
scanf("%s", &e.name);
LocateElem(L, e);
break;
case 6:
flag = 0;
printf("退出程序\n");
break;
}
}
}
五.运行截图
六.补充
可以去上一篇文章(顺序结构)查看不同方式:顺序存储方式