(数据结构)—— 基于单链表实现通讯录
- 一.通讯录的功能介绍
- 1.基于单链表实现通讯录
- (1). 知识要求
- (2). 功能要求
- 二.通讯录的代码实现
- 1.通讯录的底层结构(单链表)
- (1).思路展示
- (2).底层代码实现(单链表)
- 1.单链表头文件 —— (函数的定义)
- 2.单链表的源文件 ——(函数的实现)
- 2.通讯录上层代码实现(通讯录结构)
- (1)思路展示
- (2) 上层代码的实现(顺序表)
- 1.通讯录的头文件 —— (函数的定义)
- 2.通讯录的源文件 —— (函数的实现)
- 3.测试源文件 —— (功能函数的测试运行)
- 3.通讯录代码运行展示(数据只用于测试,无实际意义)
- (1) 测试展示
- (2) 总结升华
一.通讯录的功能介绍
1.基于单链表实现通讯录
(1). 知识要求
C语言基础要求:结构体、动态内存管理、单链表、文件件操作
(2). 功能要求
1)至少能够存储100个人的通讯信息
2)能够保存用户信息:名字、性别、年龄、电话、地址等
3)增加联系人信息
4)删除指定联系人
5)查找制定联系人
6)修改指定联系人
7)显示联系人信息
二.通讯录的代码实现
1.通讯录的底层结构(单链表)
(1).思路展示
由于,通讯录是基于单链表实现的,所以掌握基本的单链表的结构代码,非常重要~!
(2).底层代码实现(单链表)
1.单链表头文件 —— (函数的定义)
SList.h
#pragma once
#include"contact.h"
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//创建链表节点结构
typedef struct ContactInfo SLDatatype;
typedef struct SListNode
{
SLDatatype data;//要保存的数据
struct SListNode* next;
}SLNode;
//创建几个节点组成链表,并且打印链表
//打印
void SLNPrint(SLNode* phead);
//尾插
void SLNPushBack(SLNode** pphead, SLDatatype x);
//头插
void SLNPushFront(SLNode** pphead, SLDatatype x);
//尾删
void SLNPopBack(SLNode** pphead);
//头删
void SLNPopFront(SLNode** pphead);
//在指定位置插入删除
//
//查找指定的pos
SLNode* SLNFind(SLNode** pphead, SLDatatype x);
//在指定位置之前插入
void SLNInsrt(SLNode** pphead, SLNode* pos, SLDatatype x);
//在指定位置之后插入删除
void SLNInsrtAfter(SLNode* pos, SLDatatype x);
//删除指定位置的数据
void SLNErase(SLNode** pphead, SLNode* pos);
//删除指定位置之后的数据
void SLNEraseAfter(SLNode* pos);
//销毁链表
void SLNDestroy(SLNode** pphead);
2.单链表的源文件 ——(函数的实现)
SList.c
#include"SList.h"
//打印
//void SLNPrint(SLNode* phead)
//{
// //循环打印
// //可以用phead直接来访问,但是注意,当代码走到
// //第16行的时候,此时phead已经变成NULL了
// //若代码没写完,我还要继续使用指向第一个节点的地址时,这时我就
// //找不到第一个节点的地址
// //所以为了避免第一个节点被更改,要定义一个新的来代替
// SLNode* pcur = phead;
// while (pcur)
// {
// printf("%d ->", pcur->data);
// pcur = pcur->next;
// }
// printf("NULL\n");
//}
//创建新的节点
SLNode* SLByNode(SLDatatype x)
{
//开辟新的空间
SLNode* node = (SLNode*)malloc(sizeof(SLNode));
node->data = x;
node->next = NULL;
return node;
}
//尾插
void SLNPushBack(SLNode** pphead, SLDatatype x)
{
SLNode* node = SLByNode(x);
//判断链表是否为空,如果为空,直接插入
if (*pphead == NULL)
{
*pphead = node;
return;
}
//说明链表不为空 ,找尾
SLNode* pcur = *pphead;
while (pcur->next != NULL)
{
pcur = pcur->next;
}
pcur->next = node;
}
//头插
void SLNPushFront(SLNode** pphead, SLDatatype x)
{
//创建新的节点
SLNode* node = SLByNode(x);
//让新的节点跟头节点连起来
node->next = *pphead;
//再让新节点成为头节点
*pphead = node;
}
//尾删
void SLNPopBack(SLNode** pphead)
{
//先判断链表是否为空,如果为空就无法进行删除操作
assert(pphead);
//还要判断第一个节点不能为空
assert(*pphead);
//只有一个节点的情况
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
return;
}
//有多个节点的情况
//先找到尾节点/尾节点的前一个节点
SLNode* ptail = *pphead;
SLNode* prev = NULL;
while (ptail->next)
{
prev = ptail;
ptail = prev->next;
}
//让尾节点的前一个节点指向不再指向尾节点,而是指向尾节点的下一个节点
prev->next = ptail->next;
//然后在删除尾节点
free(ptail);
//为什么要将ptaii置为空指针?代码后面明明没有再使用ptail,因为打印链表的代码有判断节点的地址是否为空
ptail = NULL;
}
void SLNPopFront(SLNode** pphead)
{
//判断链表不能为空
assert(pphead);
//判断头节点不能为空
assert(*pphead);
SLNode* del = *pphead;
*pphead = (*pphead)->next;
free(del);
//出于代码规范
del = NULL;
}
//查找指定的pos
//SLNode* SLNFind(SLNode** pphead, SLDatatype x)
//{
// assert(pphead);
// SLNode* pcur = *pphead;
// while (pcur)
// {
// if (pcur->data == x)
// {
// return pcur;
// }
// pcur = pcur->next;
// }
// //出循环,代表没找到
// return NULL;
//}
//在指定位置之前插入
void SLNInsrt(SLNode** pphead, SLNode* pos, SLDatatype x)
{
SLNode* node = SLByNode(x);
assert(pphead);
assert(*pphead);
//只有一个节点的情况 == pos就是头节点,直接进行头插
if (pos == *pphead)
{
node->next = *pphead;
*pphead = node;
return;
}
//有多个节点的情况
//找pos的前一个节点
SLNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
//这里的插入节点的处理顺序可以调换
prev->next = node;
node->next = pos;
}
//在指定的位置之后插入
void SLNInsrtAfter(SLNode* pos, SLDatatype x)
{
assert(pos);
SLNode* node = SLByNode(x);
// node pos node->next
node->next = pos->next;
pos->next = node;
}
//删除指定位置的节点
void SLNErase(SLNode** pphead, SLNode* pos)
{
assert(pphead);
assert(*pphead);
assert(pos);
if (pos == *pphead)
{
*pphead = (*pphead)->next;
free(pos);
return;
}
//找pos的前一个节点
SLNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
//删除指定位置之后的节点
void SLNEraseAfter(SLNode* pos)
{
assert(pos);
assert(pos->next);
SLNode* del = pos->next;
pos->next = del->next;
free(del);
del = NULL;
}
//销毁链表
void SLNDestroy(SLNode** pphead)
{
assert(*pphead);
SLNode* pcur = *pphead;
while (pcur)
{
SLNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
2.通讯录上层代码实现(通讯录结构)
(1)思路展示
1)由于,通讯录是基于单链表实现的。
2)所以,通讯录上层代码,只需要在上层代码的头文件代码中创建一个保存联系人的结构,然后再定义通讯录功能函数即可。
3)接着在通讯录上层代码的源文件中将这些功能函数实现即可。
4)基于前面,我们已经知道,通讯录是单链表实现的,所以在实现通讯录功能函数时,只需要在通讯录上层代码的源文件中,加上单链表的头文件就可以调运单链表的函数来实现通讯录了。
(2) 上层代码的实现(顺序表)
1.通讯录的头文件 —— (函数的定义)
contact.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#pragma once
#define NAME_MAX 100
#define SEX_MAX 4
#define TEL_MAX 15
#define ADDR_MAX 100
//前置声明
typedef struct SListNode contact;
//用户数据
typedef struct ContactInfo
{
char name[NAME_MAX];
char sex[SEX_MAX];
int age;
char tel[TEL_MAX];
char addr[ADDR_MAX];
}CInfo;
//初始化通讯录
void InitContact(contact** pcon);
//添加通讯录数据
void AddContact(contact** pcon);
//删除通讯录数据
void DelContact(contact** pcon);
//展示通讯录数据
void ShowContact(contact* pcon);
//查找通讯录数据
void FindContact(contact* pcon);
//修改通讯录数据
void ModifyContact(contact** pcon);
//销毁通讯录数据
void DestroyContact(contact** pcon);
2.通讯录的源文件 —— (函数的实现)
contact.c
#include"contact.h"
#include"SList.h"
void LoadContact(contact** pcon) {
FILE* pf = fopen("contact.txt", "rb");
if (pf == NULL) {
perror("fopen error!\n");
return;
}
//循环读取文件数据
CInfo info;
while (fread(&info, sizeof(info), 1, pf))
{
SLNPushBack(pcon, info);
}
printf("历史数据导入通讯录成功!\n");
}
//初始化通讯录/建立头节点
void InitContact(contact** pcon)
{
LoadContact(pcon);
}
//添加通讯录数据
void AddContact(contact** pcon)
{
//接下来获取的都是结构体CInfo结构体里要求的数据
CInfo 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);
//联系人数据都获取到了,并保存到了结构体info中
//往通讯录(链表)中插入数据
SLNPushBack(pcon, info);
}
contact* FindByName(contact* pcon, char name[])
{
contact* cur = pcon;
while (cur)
{
if (strcmp(cur->data.name, name) == 0) {
return cur;
}
cur = cur->next;
}
return NULL;
}
//删除通讯录数据
void DelContact(contact** pcon)
{
//直接强制用户使用名称来查找
printf("请输入要删除的联系人姓名:\n");
char name[NAME_MAX];
scanf("%s", name);
contact* findidex = FindByName(*pcon, name);
if (findidex)
{
(*pcon)->next = findidex->next;
//找到了,要删除findidex位置的数据
SLNErase(pcon, findidex);
printf("删除成功\n");
}
else {
printf("要删除的联系人不存在!\n");
return;
}
}
//展示通讯录数据
void ShowContact(contact* pcon)
{
contact* cur;
cur = pcon;
//打印表头
printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "住址");
while (cur)
{
printf("%-5s %-5s %-4d %-4s %-4s\n",
cur->data.name,
cur->data.sex,
cur->data.age,
cur->data.tel,
cur->data.addr);
cur = cur->next;
}
}
//查找通讯录数据
void FindContact(contact* pcon)
{
//直接强制用户使用名称来查找
printf("请输入你要查找的联系人\n");
char name[NAME_MAX];
scanf("%s", name);
contact* findidex = FindByName(pcon, name);
if (findidex==NULL)
{
printf("没有此联系人\n");
return;
}
else
{
printf("找到了:\n");
printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "住址");
printf("%-5s %-5s %-4d %-4s %-4s\n",
findidex->data.name,
findidex->data.sex,
findidex->data.age,
findidex->data.tel,
findidex->data.addr);
}
}
//修改联系人选项
void MondifyMenu()
{
printf("***************修改联系人选项****************\n");
printf("***** 1.修改联系人名字 2.修改联系人性别*****\n");
printf("***** 3.修改联系人年龄 4.修改联系人电话*****\n");
printf("***** 5.修改联系人住址 0.退出删除选项*******\n");
printf("******************************************\n");
}
//修改通讯录数据
void ModifyContact(contact** pcon)
{
//直接强制用户使用名称来查找
printf("请输入你要修改的联系人\n");
char name[NAME_MAX];
scanf("%s", name);
contact* findidex = FindByName(*pcon, name);
if (findidex == NULL)
{
printf("没有你要删除的联系人\n");
return;
}
else {
printf("找到了:\n");
printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "住址");
printf("%-5s %-5s %-4d %-4s %-4s\n",
findidex->data.name,
findidex->data.sex,
findidex->data.age,
findidex->data.tel,
findidex->data.addr);
int val = -1;
do {
MondifyMenu();
printf("请选择你需要修改的信息:\n");
scanf("%d", &val);
switch (val)
{
case 1:
printf("请输入修改后的联系人姓名:\n");
scanf("%s", findidex->data.name);
break;
case 2:
printf("请输入修改后的联系人性别:\n");
scanf("%s", findidex->data.sex);
break;
case 3:
printf("请输入修改后的联系人年龄:\n");
scanf("%d", &findidex->data.age);
break;
case 4:
printf("请输入修改后的联系人电话:\n");
scanf("%s", findidex->data.tel);
break;
case 5:
printf("请输入修改后的联系人住址:\n");
scanf("%s", findidex->data.addr);
break;
case 0:
printf("修改成功\n");
printf("已退出删除联系人选项\n");
break;
default:
printf("没有找到要修改的选项 请重新选择:\n");
break;
}
} while (val != 0);
}
}
void SaveContact(contact* pcon) {
FILE* pf = fopen("contact.txt", "wb");
if (pf == NULL) {
perror("fopen error!\n");
return;
}
//将通讯录数据写入文件
contact* cur = pcon;
while (cur)
{
fwrite(&(cur->data), sizeof(cur->data), 1, pf);
cur = cur->next;
}
printf("通讯录数据保存成功!\n");
}
void DestroyContact(contact** pcon) {
SaveContact(*pcon);
SLNDestroy(pcon);
}
3.测试源文件 —— (功能函数的测试运行)
test.c
#include"contact.h"
#include"SList.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();
printf("请选择你要进行的操作:\n");
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:
printf("系统里没有找到你需要进行的操作,请重新输入:\n");
break;
}
} while (op != 0);
DestroyContact(&con);
return 0;
}
3.通讯录代码运行展示(数据只用于测试,无实际意义)
(1) 测试展示
(2) 总结升华
这里只是展示基于单链表实现通讯录的大致框架,和一些基本功能,有兴趣,能力的小伙伴,也可以下去拓展一下通讯录的其他功能,优化的更加完善,美观的通讯录。
感谢学习!