一.题目要求
今天来和大家分享一个简易通讯录(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; } } }
五.运行截图