C语言实现建立手机通讯录(链式结构)

一.题目要求

今天来和大家分享一个简易通讯录(C语言实现)

首先要介绍一下通讯录的基本功能

  1. 添加联系人信息
  2. 删除指定联系人信息
  3. 查找指定联系人信息
  4. 修改指定联系人信息
  5. 显示所有联系人信息

二.思路分析

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;
		}
	}
}

五.运行截图

六.补充

可以去上一篇文章(顺序结构)查看不同方式:顺序存储方式

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨城烟柳ベ旧人殇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值