C语言数据结构 ---- 单链表实现通讯录

今日备忘录: "折磨我们的往往是想象, 而不是现实."


正文开始

1. 前言

顺表实现通讯录: 点击~ 顺序表实现通讯录

在日常生活中,我们经常需要记录和管理大量的联系人信息,比如朋友的电话号码、家庭成员的生日等等。而通讯录就是一种常见的方式,用于存储和组织这些联系人信息。

通讯录可以采用不同的数据结构来实现,前面我使用了顺序表创建, 单链表是一种常用且简单的数据结构之一。单链表由一系列节点组成,每个节点包含两部分:数据域和指针域。数据域用于存储联系人的信息,指针域用于指向下一个节点。

在这篇文章中,我们将使用单链表来实现一个简单的通讯录。我们将定义联系人的结构体,包含姓名、电话号码等信息,并使用单链表来管理这些联系人。

通过单链表实现通讯录有很多好处。首先,单链表的插入和删除操作都比较高效,可以快速地添加和删除联系人。其次,通过遍历链表,我们可以方便地查找和修改联系人的信息。此外,单链表还可以动态扩展,不需要提前分配固定大小的内存空间。

接下来的文章中,我们将逐步实现通讯录的各项功能,包括添加联系人、删除联系人、查找联系人等等。通过这个实例,我们将更深入地了解单链表的原理和操作。

希望通过本文的介绍,读者能够掌握使用单链表来实现通讯录的方法,进一步提升对数据结构的理解和应用能力。让我们一起开始吧!

更多精彩, 博客主页: 酷酷学!!!

不要忘了关注哟~~


2. 通讯录的功能

1)至少少能够存储100个⼈的通讯信息
2)能够保存用户信息:名字、性别、年龄、电话、地址等
3)增加联系人信息
4)删除指定联系人
5)查找制定联系人
6)修改指定联系人
7)显示联系⼈信息


3. 通讯录的实现思路

第一步:
基于单链表的基础上, 我们再创建两个文件: Contact.h 和 Contact.c, 一个用来声明通讯录的结构和函数声明, 一个用来实现通讯录的功能.

在这里插入图片描述
第二步:
创建通讯录头文件, 把需要用到的结构定义和函数声明都先罗列出来:

其中需要前置声明更改链表的名字为contact.

注意: 这里为什么要前置声明呢?
因为头文件不可以嵌套包含, 这样会使程序代码冗余, 在单链表的头文件包含了通讯录的头文件, 就不要在让通讯录的头文件包含单链表了, 单链表中需要使用到通讯录的结构定义, 如下所示:

SList.h
需要更改存储数据类型为通讯录

#include"Contact.h"

typedef PeoInfo Datatype;

typedef struct SListNode
{
	Datatype data;
	struct SListNode* next;
}SNode;

Contact.h

#pragma once

#define NAME_MAX 100
#define SEX_MAX 4
#define TEL_MAX 11
#define ADDR_MAX 100

typedef  struct SListNode contact;//前置声明

typedef struct PersonInfo
{
	char name[NAME_MAX];
	char sex[SEX_MAX];
	int age;
	char tel[TEL_MAX];
	char addr[ADDR_MAX];
}PeoInfo;

//初始化,把之的数据导入
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 DestoryContact(contact** con);

第三步:
通讯录的功能实现:
其中需要包含两个头文件, 其实通讯录底层的方法就是用到了单链表的方法, 所以这里我们都需要包含:

#include"Contact.h"
#include"SList.h"
  1. 初始化: 就是将原来的数据导入到新的通讯录之中

这里简单回顾一下二进制的文件操作函数, 更多详情可以查看cpp官网Cplusplus

	//fread从二进制文件中读取数据,

	//fread函数:
	// 以数组举例:  
	//size_t fread(const void* ptr, size_t size, size_t count, FILE* stream);
	//可以理解为一个读取到一个指向首元素的数组, ptr是首元素的地址, 
	//size为一个元素的大小,单位字节, count为一个要读取多少个元素, stream为读取的那个文件的文件流
	//首先得有一个大于count字节数组,然后把首地址给到fread,让fread读取到的数据保存到这个数组中
	//如:
	//doubule b[size]
	//fread(b, sizeof * b, size, "fp");

	// fwrite函数:
	// size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);
	// 理解为写入到文件中, ptr为数组首元素, size写入的每个元素的字节, count写入多少个这样的元素, stream文件流
	// 
void LoadContact(contact** con)
{
	FILE* pf = fopen("contact.txt", "rb");//二进制只读,不创建新文件
	if (pf == NULL)
	{
		perror("fopen error!\n");
		return;
	}
	//打开成功
	PeoInfo info;//创建一个通讯录变量
	while (fread(&info, sizeof(info), 1, pf))
		//读取成功返回count,读取不成功返回0
	{
		PushBack(con, info);
	}
	printf("历史数据导入通讯录成功\n");
}

void InitContact(contact** con)
{
	LoadContact(con);
}
  1. 通讯录的增加

简单理解, 创建一个通讯录的类型的变量, 根据用户的输入进行初始化, 完成之后调用单链表尾插法.

void AddContact(contact** con)
{
	PeoInfo info;
	printf("请输入姓名: \n");
	scanf("%s",info.name);
	printf("请输入性别: \n");
	scanf("%s",info.sex);
	printf("请输入年龄: \n");
	scanf("%d",&info.age);
	printf("请输入电话: \n");
	scanf("%s",info.tel);
	printf("请输入地址: \n");
	scanf("%s",info.addr);

	PushBack(con, info);
	printf("插入成功!\n");
}
  1. 通讯录的删除

这里实现的是根据姓名, 当然也可以其他方式, 但逻辑都是一样的, 单独封装一个根据姓名查找的函数, 变量查找, 并返回节点, 如果找到了就直接调用单链表指定位置删除函数,这里也不难发现, 其实通讯录就是基于单链表进行套娃.

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

//根据姓名来删除
void DelContact(contact** con)
{
	char name[NAME_MAX];
	printf("请输入要删除的用户姓名:\n");
	scanf("%s",name);

	contact* pos = FindByName(*con,name);
	if (pos == NULL)
	{
		printf("要删除的用户不存在,删除失败\n");
		return;
	}
	Erase(con,pos);
	printf("删除成功\n");
}

  1. 展示通讯录

占位符保持一致, -n%,-表示右对齐,n表示多少字符对齐, 变量展示数据.

void ShowContact(contact* con)
{
	printf("%-10s %-4s %-4s %-15s %-20s\n", "姓名", "性别", "年龄", "联系电话", "地址");
	contact* cur = con;
	while (cur)
	{
		printf("%-10s %-4s %-4d %-15s %-20s\n",
			cur->data.name,
			cur->data.sex,
			cur->data.age,
			cur->data.tel,
			cur->data.addr);
		cur = cur->next;
	}
}
  1. 查找通讯录

因为我们封装过姓名查找的函数方法了, 所以这里就可以直接使用查找姓名的方式来显示联系人,
找到之后打印数据.

void FindContact(contact* con)
{
	char name[NAME_MAX];
	printf("请输入要查找的用户姓名:\n");
	scanf("%s",name);

	contact* pos = FindByName(con,name);
	if (pos == NULL)
	{
		printf("要查找的用户不存在,查找失败\n");
		return;
	}
	printf("查找成功\n");
	printf("%-10s %-4s %-4s %-15s %-20s\n", "姓名", "性别", "年龄", "联系电话", "地址");
	printf("%-10s %-4s %-4d %-15s %-20s\n",
		pos->data.name,
		pos->data.sex,
		pos->data.age,
		pos->data.tel,
		pos->data.addr);
}
  1. 修改通讯录

根据姓名就行修改, 先查找,找到之后直接修改就行.

void ModifyContact(contact** con)
{
	char name[NAME_MAX];
	printf("请输入要修改的用户名称:\n");
	scanf("%s",&name);

	contact* pos = FindByName(*con, name);
	if (pos == NULL)
	{
		printf("要查找的用户不存在,修改失败\n");
		return;
	}

	printf("请输入要修改的姓名: \n");
	scanf("%s",pos->data.name);
	printf("请输入要修改的性别: \n");
	scanf("%s",pos->data.sex);
	printf("请输入要修改的年龄: \n");
	scanf("%d",&pos->data.age);
	printf("请输入要修改的电话: \n");
	scanf("%s",pos->data.tel);
	printf("请输入要修改的地址: \n");
	scanf("%s",pos->data.addr);

	printf("修改成功\n");
}
  1. 通讯录扫尾

先保存数据, 封装一个函数保存数据, 以二进制的形式循环的输入到指定文件, 知道循环结束,最后调用单链表销毁函数, 释放内存

void SaveContact(contact* con)
{
	FILE* pf = fopen("contact.txt", "wb");
	if (pf == NULL)
	{
		perror("fopen error\n");
		return;
	}
	contact* cur = con;
	while (cur)
	{
		fwrite(&(cur->data), sizeof(cur->data), 1, pf);
		cur = cur->next;
	}
	printf("通讯录数据保存成功\n");
}

void DestoryContact(contact** con)
{
	SaveContact(*con);
	SListDesTory(con);
}

第四步:

进行主函数的封装:

SLIst.h 已经包含了Contact.h所以我们直接包含SList.h就可以了, 封装一个函数打印初始菜单,我这里没有进行初始化, 是因为没有之前的数据可导入, 需要的也可自行加上.


#define _CRT_SECURE_NO_WARNINGS 1

#include"SList.h"
void menu()
{
	printf("********************通讯录*********************\n");
	printf("********1.增加联系人    2.删除联系*************\n");
	printf("********3.修改联系人    4.查找联系人***********\n");
	printf("********5.展示联系人    0.退出*****************\n");
	printf("***********************************************\n");
}


int main()
{
	//test01();
	int op = 0;
	contact* con = NULL;
	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);
	DestoryContact(&con);
	return 0;
}

5. 效果展示

下面是我在控制台界面自行测试的结果,需要其他效果也可自行修改

在这里插入图片描述

在这里插入图片描述


6. 完整代码

SList.h

#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include"Contact.h"

typedef PeoInfo Datatype;

typedef struct SListNode
{
	Datatype data;
	struct SListNode* next;
}SNode;

//void SLTPrint(SNode* phead);

void PushBack(SNode** pphead, Datatype x);
void PushFront(SNode** pphead, Datatype x);
void PopBack(SNode** pphead);
void PopFront(SNode** pphead);

//SNode* FindNode(SNode* phead, Datatype x);
void SLTInsert(SNode** pphead, SNode* pos, Datatype x);
void SLTInsertAfter(SNode* pos, Datatype x);

void Erase(SNode** pphead, SNode* pos);
void SlTErase(SNode* pos);

void SListDesTory(SNode** pphead);

SList.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"SList.h";

//void SLTPrint(SNode* phead)
//{
//	SNode* pcur = phead;
//	while (pcur)
//	{
//		printf("%d -> ", pcur->data);
//		pcur = pcur->next;
//	}
//	printf("NULL\n");
//}

SNode* BuyNode(Datatype x)
{
	SNode* NewNode = (SNode*)malloc(sizeof(SNode));
	if (NewNode == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	NewNode->data = x;
	NewNode->next = NULL;
	return NewNode;
}

void PushBack(SNode** pphead, Datatype x)
{
	assert(pphead);
	SNode* NewNode = BuyNode(x);
	SNode* pcur = *pphead;

	if (pcur == NULL)
	{
		*pphead = BuyNode(x);
		*pphead = NewNode;
	}
	else
	{
		while (pcur->next)
		{
			pcur = pcur->next;
		}
		pcur->next = NewNode;
	}
}

void PushFront(SNode** pphead, Datatype x)
{
	assert(pphead);
	SNode* NewNode = BuyNode(x);
	NewNode->next = *pphead;
	*pphead = NewNode;
}


void PopBack(SNode** pphead)
{
	assert(pphead && *pphead);

	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SNode* ptail = *pphead;
		SNode* prev = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
		prev->next = NULL;
	}
}

void PopFront(SNode** pphead)
{
	assert(pphead && *pphead);
	SNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

void SLTInsert(SNode** pphead, SNode* pos, Datatype x)
{
	assert(pphead && *pphead);
	assert(pos);

	SNode* NewNode = BuyNode(x);
	if (pos == *pphead)
	{
		PushFront(pphead, x);
	}
	else
	{
		SNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		NewNode->next = pos;
		prev->next = NewNode;
	}
}

void SLTInsertAfter(SNode* pos, Datatype x)
{
	assert(pos);
	SNode* NewNode = BuyNode(x);

	NewNode->next = pos->next;
	pos->next = NewNode;
}

void Erase(SNode** pphead, SNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);
	if (pos == *pphead)
	{
		PopFront(pphead);
	}
	else {
		SNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//prev pos pos->next
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

void SlTErase(SNode* pos)
{
	assert(pos && pos->next);
	SNode* del = pos->next;
	//pos del del->next
	pos->next = del->next;
	free(del);
	del = NULL;
}

void SListDesTory(SNode** pphead)
{
	assert(pphead && *pphead);

	SNode* pcur = *pphead;
	while (pcur)
	{
		SNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//pcur
	*pphead = NULL;
}

Contact.c

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

void LoadContact(contact** con)
{
	FILE* pf = fopen("contact.txt", "rb");//二进制只读,不创建新文件
	if (pf == NULL)
	{
		perror("fopen error!\n");
		return;
	}
	//打开成功
	PeoInfo info;//创建一个通讯录变量
	while (fread(&info, sizeof(info), 1, pf))
		//读取成功返回count,读取不成功返回0
	{
		PushBack(con, info);
	}
	printf("历史数据导入通讯录成功\n");
}

void InitContact(contact** con)
{
	LoadContact(con);
}

void AddContact(contact** con)
{
	PeoInfo info;
	printf("请输入姓名: \n");
	scanf("%s",info.name);
	printf("请输入性别: \n");
	scanf("%s",info.sex);
	printf("请输入年龄: \n");
	scanf("%d",&info.age);
	printf("请输入电话: \n");
	scanf("%s",info.tel);
	printf("请输入地址: \n");
	scanf("%s",info.addr);

	PushBack(con, info);
	printf("插入成功!\n");
}

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

//根据姓名来删除
void DelContact(contact** con)
{
	char name[NAME_MAX];
	printf("请输入要删除的用户姓名:\n");
	scanf("%s",name);

	contact* pos = FindByName(*con,name);
	if (pos == NULL)
	{
		printf("要删除的用户不存在,删除失败\n");
		return;
	}
	Erase(con,pos);
	printf("删除成功\n");
}

void ShowContact(contact* con)
{
	printf("%-10s %-4s %-4s %-15s %-20s\n", "姓名", "性别", "年龄", "联系电话", "地址");
	contact* cur = con;
	while (cur)
	{
		printf("%-10s %-4s %-4d %-15s %-20s\n",
			cur->data.name,
			cur->data.sex,
			cur->data.age,
			cur->data.tel,
			cur->data.addr);
		cur = cur->next;
	}
}

void FindContact(contact* con)
{
	char name[NAME_MAX];
	printf("请输入要查找的用户姓名:\n");
	scanf("%s",name);

	contact* pos = FindByName(con,name);
	if (pos == NULL)
	{
		printf("要查找的用户不存在,查找失败\n");
		return;
	}
	printf("查找成功\n");
	printf("%-10s %-4s %-4s %-15s %-20s\n", "姓名", "性别", "年龄", "联系电话", "地址");
	printf("%-10s %-4s %-4d %-15s %-20s\n",
		pos->data.name,
		pos->data.sex,
		pos->data.age,
		pos->data.tel,
		pos->data.addr);
}

void ModifyContact(contact** con)
{
	char name[NAME_MAX];
	printf("请输入要修改的用户名称:\n");
	scanf("%s",&name);

	contact* pos = FindByName(*con, name);
	if (pos == NULL)
	{
		printf("要查找的用户不存在,修改失败\n");
		return;
	}

	printf("请输入要修改的姓名: \n");
	scanf("%s",pos->data.name);
	printf("请输入要修改的性别: \n");
	scanf("%s",pos->data.sex);
	printf("请输入要修改的年龄: \n");
	scanf("%d",&pos->data.age);
	printf("请输入要修改的电话: \n");
	scanf("%s",pos->data.tel);
	printf("请输入要修改的地址: \n");
	scanf("%s",pos->data.addr);

	printf("修改成功\n");
}

void SaveContact(contact* con)
{
	FILE* pf = fopen("contact.txt", "wb");
	if (pf == NULL)
	{
		perror("fopen error\n");
		return;
	}
	contact* cur = con;
	while (cur)
	{
		fwrite(&(cur->data), sizeof(cur->data), 1, pf);
		cur = cur->next;
	}
	printf("通讯录数据保存成功\n");
}

void DestoryContact(contact** con)
{
	SaveContact(*con);
	SListDesTory(con);
}

Contact.h

#pragma once

#define NAME_MAX 100
#define SEX_MAX 4
#define TEL_MAX 11
#define ADDR_MAX 100

typedef  struct SListNode contact;

typedef struct PersonInfo
{
	char name[NAME_MAX];
	char sex[SEX_MAX];
	int age;
	char tel[TEL_MAX];
	char addr[ADDR_MAX];
}PeoInfo;

//初始化,把之的数据导入
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 DestoryContact(contact** con);

test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"SList.h"

//void test01()
//{
//	SNode* plist = NULL;
//	PushBack(&plist, 200);
//	PushFront(&plist, 300);
//
//	PopBack(&plist);
//
//	SLTPrint(plist);
//}

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


int main()
{
	//test01();
	int op = 0;
	contact* con = NULL;
	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);
	DestoryContact(&con);
	return 0;
}

7. 总结

总的来说,单链表是一种简单且灵活的数据结构,非常适合实现通讯录功能。它可以实现通讯录的常见操作,并且具有较好的扩展性和性能优化的空间.

后续更多精彩, 请持续关注~

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

酷酷学!!!

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

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

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

打赏作者

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

抵扣说明:

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

余额充值