(包会)再战通讯录(单链表版)

本文介绍了如何使用链表数据结构实现通讯录功能,包括链表的节点定义、操作函数(如头插、尾插、查找和删除)以及通讯录类的配合。通过逐步实现,读者可以复习链表知识并理解通讯录管理的基本原理。
摘要由CSDN通过智能技术生成

1.前言

    好久不见,甚是想念!最近因为大过年+开学好久没有更新了,接下来就更新又一个保姆级教学----也就是我们的通讯录的第二个版本,也就是用链表实现,话不多说,咱们直接开干!


2.代码实现

    首先,东西还是那几个东西,在实现通讯录的时候,我们当然也要写一个单链表,所以,通过这篇文章,我们也可以好好的复习一下链表哦,不会的同学可以好好复习一下。我们可以用SList.c和SList.h来代表链表的实现,然后紧接着又是我们熟悉的Contact.c、Contact.h,最后就是我们的测试文件Contest.c,总共还是五个文件。(tips:建议看完我上一篇博客也就是顺序表实现通讯录再来看这里,会有不同的收获)。

    接下来就可以开始写代码啦!还是老样子,代码和注释都在一起,灰常详细哦,同学们细品,有哪里有疑问可以在评论区留言哦(个人实现,如有不足请多多指教)!

    tips:由于通讯录的注释在上一篇博客中注释了,所以这一次就没有太多注释咯,思路基本是一样的,就是实现方式不同,如果有不会的建议先看看我的上一篇博客哦!多多支持!

//SList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include"Contact.h"

typedef struct PersonInfo SLTDataType;
typedef struct SListNode
{
	SLTDataType data;  //节点数据
	struct SListNode* next;  //指针保存下一个节点的地址
}SLTNode;

//打印单链表
void SLTPrint(SLTNode* phead);
//开辟一个新节点
SLTNode* SLTBuyNode(SLTDataType x);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找
void SLTFind(SLTNode** pphead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsertForward(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除指定位置之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SLTDestroy(SLTNode** pphead);

//SList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"

void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;  //让phead一直指向第一个数据,让pcur遍历链表
	while (pcur)  //当pcur不为空的时候执行循环
	{
		printf("%d->", pcur->data);  //打印pcur指向的当前数据
		pcur = pcur->next;  //然后指向下一个节点的地址
	}
	printf("NULL\n");
}

SLTNode* SLTBuyNode(SLTDataType x)  //为x开辟一个专用的节点
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));  //用一个malloc开辟大小为SLTNode的新节点
	newnode->data = x;  //将要插入的数据x放入新节点中
	newnode->next = NULL;  //当然新节点的下一个节点是空
	return newnode;  //返回去要插入的新节点
}

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);  //返回新节点
	//如果链表为空,新节点作为phead
	if (*pphead == NULL)
	{
		*pphead = newnode;
		return;
	}
	//链表不为空,找尾节点
	SLTNode* ptail = *pphead;  //ptail表示的是尾节点,所以ptail的next表示的肯定要是空
	while (ptail->next)  //只有ptail的下一个不为空才能进行循环
	{
		ptail = ptail->next;  //遍历链表,找到ptail下一个为空的节点,就是末节点
	}
	ptail->next = newnode;
}

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	//头插的话,链表为空和不为空都是在前面插,把新的节点当作新的phead,所以不用分情况
	SLTNode* newnode = SLTBuyNode(x);  //传新节点进来
	newnode->next = *pphead;  //新节点的下一个必定是原来的phead
	*pphead = newnode;  //新节点作为新的phead
}

void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	//链表不能为空,空的话不能删
	assert(*pphead);
	//如果链表只有一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);  //直接free掉头,就相当于删除
		*pphead = NULL;
		return;
	}
	//如果链表有多个节点
	SLTNode* ptail = *pphead;  //ptail就是尾节点,是即将要删除的点
	SLTNode* prev = NULL;  //prev就是尾节点的前一个节点
	while (ptail->next)  //遍历链表,让ptail是尾节点
	{
		prev = ptail;  
		ptail = ptail->next;  //使prev成为ptail的前一个节点
	}
	prev->next = NULL;  //ptail是要删除的点,删除掉ptail后,prev就是新的尾节点,所以prev的下一个一定是空
	//销毁尾节点
	free(ptail);
	ptail = NULL;
}

void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);
	//链表不能为空
	assert(*pphead);
	//让第二个节点成为新的头,原来的头节点释放掉
	SLTNode* newhead = (*pphead)->next;  //新的头是旧头的下一个
	free(*pphead);  //把旧的头释放掉
	*pphead = newhead;  //新的头成为新头节点
}

//void SLTFind(SLTNode** pphead, SLTDataType x)
//{
//	assert(pphead);
//	//遍历链表
//	SLTNode* pcur = *pphead;  //定义一个pcur遍历链表
//	while (pcur)
//	{
//		if (pcur->data == x)  //如果pcur指向的节点是x的节点,说明找到了
//		{
//			//说明找到了
//			return pcur;
//		}
//		pcur = pcur->next;
//	}
//	//循环结束还未找到
//	return NULL;
//}

void SLTInsertForward(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);
	assert(*pphead);  //由于pos不能为空,所以链表也不能为空
	//如果pos刚好是头节点
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
		return;
	}
	//如果pos不是头节点
	SLTNode* prev = *pphead;
	while (prev->next != pos)  //遍历链表,找pos之前的节点prev
	{
		prev = prev->next;
	}
	SLTNode* newnode = SLTBuyNode(x);  //创建要插入的节点
	prev->next = newnode;  //把要插入的节点插到原来在pos之前的节点prev后面
	newnode->next = pos;  //插入节点的后面当然就是pos
}

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);  //先为x开辟新节点
	newnode->next = pos->next;
	pos->next = newnode;
}

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);
	assert(*pphead);
	//如果pos刚好是头节点,就无prev,直接执行头删
	if (pos == *pphead)
	{
		SLTPopFront(pphead);
		return;
	}
	SLTNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;  //寻找pos的前驱节点prev
	}
	prev->next = pos->next;  //要先写这一步的原因是pos如果提前释放了就找不到pos的下一个了
	free(pos);
	pos = NULL;  //删除pos节点
}

void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);  //pos->next不能为空,否则无法执行删除
	SLTNode* del = pos->next;  //定义del为要删除的节点
	pos->next = pos->next->next;  //所以pos要连接的节点就是del的下一个节点
	free(del);
	del = NULL;  //删除pos的下一个节点
}

void SLTDestroy(SLTNode** pphead)
{
	assert(pphead);
	//assert(*pphead);
	//由于链表是一个一个节点构成的,无法像顺序表一样一次性销毁,要一个一个节点的进行销毁
	SLTNode* pcur = *pphead;  //定义一个pcur遍历链表,并一个一个销毁
	while (pcur)
	{
		SLTNode* next = pcur->next;  //先从第二个开始进行销毁
		free(pcur);
		pcur = next;  //每完成一个指向下一个节点
	}
	*pphead = NULL;  //不要忘了还有头节点未销毁
}

//Contact.h
#pragma once
#pragma once
#define NAME_MAX 100
#define SEX_MAX 6
#define TEL_MAX 12
#define ADDR_MAX 100

//前置声明
typedef struct SListNode contact;

//用户数据
typedef struct PersonInfo
{
    char name[NAME_MAX];
    char gender[SEX_MAX];
    int age;
    char phone[TEL_MAX];
    char addr[ADDR_MAX];
}Info;

//初始化通讯录
void InitContact(contact** con);
//添加通讯录数据
void AddContact(contact** con);
//删除通讯录数据
void DelContact(contact** con);
//展示通讯录数据
void ShowContact(contact* con);
//查找通讯录数据
void FindContact(contact* con);
//修改通讯录数据
void ModifyContact(contact** con);
//销毁通讯录数据
void DestroyContact(contact** con);

//Contact.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Contact.h"
#include"SList.h"

void InitContact(contact** con)
{
	//哨兵位
	contact* head = (contact*)malloc(sizeof(contact));
	head->next = NULL;
}

void AddContact(contact** con)
{
	Info info;
	printf("请输入联系人姓名:\n");
	scanf("%s", &info.name);
	printf("请输入联系人性别:\n");
	scanf("%s", &info.gender);
	printf("请输入联系人年龄:\n");
	scanf("%d", &info.age);
	printf("请输入联系人电话:\n");
	scanf("%s", &info.phone);
	printf("请输入联系人地址:\n");
	scanf("%s", &info.addr);
	SLTPushBack(con, info);
}

contact* FindByName(contact* con,char name[])
{
	contact* pcur = con;
	while (pcur)
	{
		if (strcmp(pcur->data.name, name) == 0)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

void DelContact(contact** con)
{
	printf("请输入要删除的联系人姓名:\n");
	char name[NAME_MAX];
	scanf("%s", name);
	contact* ret = FindByName(*con, name);
	if (ret == NULL)
	{
		printf("用户不存在!\n");
		return;
	}
	SLTErase(con, ret);
	printf("删除成功!\n");
}

void ShowContact(contact* con)
{
	printf("%s\t%s\t%s\t%s\t%s\n", "姓名", "性别", "年龄", "电话", "地址");
	contact* pcur = con;
	while (pcur)
	{
		printf("%s %s %d %s %s\n", pcur->data.name, pcur->data.gender, pcur->data.age, pcur->data.phone, pcur->data.addr);
		pcur = pcur->next;
	}
}

void FindContact(contact* con)
{
	char name[NAME_MAX];
	printf("请输入要查找的联系人姓名:\n");
	scanf("%s", name);
	contact* ret = FindByName(con, name);
	if (ret == NULL)
	{
		printf("用户不存在!\n");
		return;
	}
	printf("%s\t%s\t%s\t%s\t%s\n", "性名", "性别", "年龄", "电话", "地址");
	printf("%s %s %d %s %s\n", ret->data.name, ret->data.gender, ret->data.age, ret->data.phone, ret->data.addr);
}

void ModifyContact(contact** con)
{
	char name[NAME_MAX];
	printf("请输入要修改的联系人姓名:\n");
	scanf("%s", &name);
	contact* ret = FindByName(*con, name);
	if (ret == NULL)
	{
		printf("用户不存在!\n");
		return;
	}
	printf("请输入联系人姓名:\n");
	scanf("%s", ret->data.name);
	printf("请输入联系人性别:\n");
	scanf("%s", ret->data.gender);
	printf("请输入联系人年龄:\n");
	scanf("%d", &ret->data.age);
	printf("请输入联系人电话:\n");
	scanf("%s", ret->data.phone);
	printf("请输入联系人地址:\n");
	scanf("%s", ret->data.addr);
}

void DestroyContact(contact** con)
{
	SLTDestroy(con);
}


//Contest.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
#include"Contact.h"

//通讯录菜单
void menu()
{
	printf("*******************   通讯录   ********************\n");
	printf("******1.添加联系人                2.删除联系人*****\n");
	printf("******3.修改联系人                4.查找联系人*****\n");
	printf("******5.查看通讯录                0.退出通讯录*****\n");
	printf("***************************************************\n");
}


int main()
{
	int op = -1;
	//创建通讯录结构对象
	contact* con = NULL;
	InitContact(&con);
	do
	{
		menu();
		scanf("%d", &op);
		switch (op)
		{
		case 1:
			//添加联系人
			AddContact(&con);
			break;
		case 2:
			//删除联系人
			DelContact(&con);
			break;
		case 3:
			//修改联系人
			ModifyContact(&con);
			break;
		case 4:
			//查找联系人
			FindContact(con);
			break;
		case 5:
			//查看通讯录
			ShowContact(con);
			break;
		case 0:
			//退出通讯录
			printf("您已退出您的通讯录.....\n");
			break;
		default:
			break;
		}
	} while (op);
	//销毁通讯录
	DestroyContact(&con);
	return 0;
}


品玩了吗?是不是很有收获呢?相信你对链表有了很大的理解了吧!咱们下期见!每天干货满满!

  • 14
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

七分七分_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值