单链表实现通讯录
之前我们完成了基于顺序表(动态)实现通讯录,现在我们链表学完了,可以尝试着使用链表来实现我们的通讯录。
首先我们要明白我们写的通讯录是由一个个节点组成的,每个节点里存储的就是我们的联系人信息。也就是说 我们需要先写一个单链表,完成单链表的插入,删除等功能。然后在单链表的基础上将单链表中的每个节点存储的数据改为一个联系人 由于这一部分我们之前写过,所以我在这里就不重复写了,大家可以看下我前面的博客。
一、通讯录的具体形式
二、实现通讯录
(1)、确定链表的类型
在我的双向链表这篇博客里我们了解了链表的分类,我们既写过不带头单向不循环链表,又写过带头双向循环链表。那么我们该如何选择呢?-在这里其实两种链表都可以,难度也是差不多的,因为双链表相较于单链表完善许多,那我们就用单链表来写。
(2)、完成通讯录的思路
首先,通讯录需要完成我们的 增、删联系人,修改联系人,查找联系人,打印通讯录。 这一部分就是我们需要完成的功能。
准备阶段:上面我们知道我们是由一个结构体来储存我们的联系人信息,我们就需要定义一个person联系人的结构体,然后我们之前写的链表里每个节点存储的数据的类型是int型,我们需要将其改为我们现在的person型。联系人的增删查改就是调用我们之前写的单链表的增删等函数,查改这两部分略有差异需要重新来写。
(3)、准备阶段:
contact.h
#define NAME_MAX 20
#define GENDER_MAX 10
#define ADDR_MAX 100
#define NUM_MAX 15
typedef struct person
{char name[NAME_MAX];//姓名char gender[GENDER_MAX];//性别int age;//年龄char addr[ADDR_MAX];//住址char num[NUM_MAX];//电话号码
}person;。
void ConAdd(contact** ptr);//联系人添加
void ConPrint(contact** ptr);//联系人打印
void ConPop(contact** ptr);//删除联系人
void ConFind(contact** ptr);//查找联系人
void ConGai(contact** ptr);//修改联系人
void ConDesTroy(contact** ptr);//销毁通讯录
ListNode.h
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include"单链表contact.h"
typedef person datatype;
typedef struct ListNode
{datatype data;struct ListNode* next;
}ListNode;
void SLprint(ListNode** ptr);//打印
void SLBackPush(ListNode** ptr,datatype x);//单链表的尾插
void SLProntPush(ListNode** ptr, datatype x);//单链表的头插
void SLBackPop(ListNode** ptr);//单链表的尾删
void SLProntPop(ListNode** ptr);//单链表的头删
ListNode* SLFind(ListNode** ptr,datatype x);//链表的查找
void SLPosPush(ListNode** ptr, ListNode* pos, datatype x);//在指定位置插入数据
void SLPosPop(ListNode** ptr,ListNode* pos);//删除指定节点
void SLDesTory(ListNode** ptr);//销毁链表
这里我们需要注意的是因为我们需要将每个结构体里的数据类型换为person那么我们就需要包含contact.h的头文件,但是我们知道我们使用通讯录时其实是调用的外层的单链表结构,所以我们需要将ListNode.h包含在contact.h里,现在就出现了一个尴尬的情况–互相包含。解决的办法就是前置申明,在contact.h中typedef struct ListNode contact;这一段代码不光前置声明了还将外层的链表给改个名。
(4)、增删查改的实现
1、增加联系人,上面我们说过增加联系人就是调用我们的单链表的插入函数,我们写单链表的时候写了单链表的头插与尾插,在这里都可以,但是为了更符合日常我在这里采用尾插。
void ConAdd(contact** ptr)
{assert(ptr);printf("请输入添加的联系人的姓名 性别 年龄 住址 电话\n");person pes;scanf("%s", pes.name);scanf("%s", pes.gender);scanf("%d", &pes.age);//age是整型所以需要&符号scanf("%s", pes.addr);scanf("%s", pes.num);SLBackPush(ptr, pes);
}
这里需要注意的是我们是将整个person结构体定义的变量插入到链表,所以我们需要定义一个联系人的变量,先将联系人赋值,然后再将这整个变量作为我们的数据插入。
2、删除联系人
void ConPop(contact** ptr)
{assert(ptr && *ptr);char NAME[NAME_MAX];//定义一个数组printf("请输入你要删除的联系人的姓名:\n");scanf("%s", NAME);//输入我们要删除的联系人的姓名contact* pcur = *ptr;while (pcur){if (strcmp(pcur->data.name,NAME)==0)//比较输入的字符串和链表中的联系人姓名,名字相同时删除该联系人{if (pcur==*ptr)//当删除的联系人为第一个节点{pcur = pcur->next;free(*ptr);*ptr = pcur;break;}else//删除的联系人不在第一个节点{contact* prev = *ptr;while (prev->next != pcur)//找需要删除的联系人的前一个节点{prev = prev->next;}prev->next = pcur->next;free(pcur);pcur = NULL;break;}}pcur = pcur->next;}
}
删除联系人稍微比较麻烦,首先我们要确定以什么为标准来删除联系人,如果以电话号码来删除,就需要比较两个电话号码的字符串,名字同理,为了符合常理我这里使用名字,确定了以什么为标准以后我们还要考虑需要删除的联系人是否为第一个节点。
如果不为第一个节点,就需要遍历整个链表,找到需要删除的联系人的前一个节点,然后将前一个节点与后一个节点连接,最后释放掉需要删除的节点即可。
如果需要删除的联系人就是第一个节点的话,我们就不需要找前一个节点,直接让指向头结点的指针向后移,然后释放掉原来的头结点。
以上就是删除时的三种情况,第三种只有一个联系人的情况是可以通过第二种方式的代码解决。
3、查找联系人
同样查找联系人也需要我们以一个标准来选,我这里依然用名字来比较。
void ConFind(contact** ptr)
{assert(ptr && *ptr);char NAME[NAME_MAX];printf("请输入你要查找的联系人的姓名:\n");scanf("%s", NAME);contact* pcur = *ptr;while (pcur){if (strcmp(pcur->data.name, NAME) == 0){printf("姓名 性别 年龄 住址 电话\n");printf("%s %s %d %s %s\n", pcur->data.name,pcur->data.gender,pcur->data.age,pcur->data.addr,pcur->data.num);}pcur = pcur->next;}
}
这一部分简单许多,直接遍历链表,找到名字相同的节点,然后将该节点存储的联系人信息打印出来即可。
4、修改联系人
同样我这里用名字作为找联系人的标准。
void ConGai(contact** ptr)
{assert(ptr && *ptr);char NAME[NAME_MAX];printf("请输入你要修改的联系人的姓名:\n");scanf("%s", NAME);contact* pcur = *ptr;while (pcur){if (strcmp(pcur->data.name, NAME) == 0){printf("请输入修改后的联系人的姓名 性别 年龄 住址 电话\n");scanf("%s %s %d %s %s",pcur->data.name,pcur->data.gender,&pcur->data.age,pcur->data.addr,pcur->data.num);}pcur = pcur->next;}
}
这里跟上面的找联系人相似,找到后直接输入修改后的联系人的信息,覆盖掉原联系人的信息即可。
(5)、通讯录的销毁
当我们使用完通讯录,我们就需要将这个通讯录的空间给释放掉,那之前我们说有销毁就有初始化,为什么通讯录没有初始化呢?那是因为我们使用这个链表时,使用的是指向这个链表的指针,当我们不进行增删查改等操作时,该指针就为空,不需要进行初始化。
void ConDesTroy(contact** ptr)
{assert(ptr && *ptr);contact* pcur = *ptr;while (pcur){contact* del = pcur->next;free(pcur);pcur = del;}pcur = NULL;
}
销毁链表也比较简单,直接遍历整个链表,遍历一个释放一个。
上面我们完成了通讯录的功能,那么我们就可以将功能组装起来成为我们的通讯录。
#include"ListNode.h"
#include"单链表contact.h"
void menu()
{printf("***************************************\n");printf("*************单链表通讯录**************\n");printf("*******1、添加联系人 2、删除联系人****\n");printf("*******3、查找联系人 4、修改联系人****\n");printf("*******5、打印联系人 0、退出通讯录****\n");printf("***************************************\n");
}int main()
{contact* phead=NULL;int input = 0;do{menu();printf("请输入你的选项:\n");scanf("%d", &input);switch (input){case 1:ConAdd(&phead);break;case 2:ConPop(&phead);break;case 3:ConFind(&phead);break;case 4:ConGai(&phead);break;case 5:ConPrint(&phead);break;case 0:ConDesTroy(&phead);printf("退出成功");break;default:printf("选项错误,请重新输入:");break;}} while (input);return 0;
}
像这样我们就可以通过输入的数字来进行我们的通讯录操作。
上面我已经给出了contact.h ListNode.h test.c的代码,下面是其他部分的代码:
ListNode.c
#include"ListNode.h"ListNode* buynode(datatype x)
{ListNode* node = (ListNode*)malloc(sizeof(ListNode));if (node == NULL){perror("malloc");exit(1);}node->data = x;node->next = NULL;return node;
}void SLBackPush(ListNode** ptr,datatype x)
{assert(ptr);ListNode* node = buynode(x);if (*ptr == NULL){*ptr = node;}else{ListNode* ptail = *ptr;while (ptail->next != NULL){ptail = ptail->next;}ptail->next = node;}
}//void SLprint(ListNode** ptr)
//{
// assert(ptr);
// ListNode* pcur = *ptr;
// while (pcur)
// {
// printf("%d ", pcur->data);
// pcur = pcur->next;
// }
// printf("\n");
//}void SLProntPush(ListNode** ptr, datatype x)
{assert(ptr);ListNode* node = buynode(x);if (*ptr == NULL){*ptr = node;}else{node->next = *ptr;*ptr = node;}
}void SLBackPop(ListNode** ptr)
{assert(ptr);if ((*ptr)->next==NULL){free(*ptr);*ptr = NULL;}else{ListNode* pcur = *ptr;while (pcur->next->next){pcur = pcur->next;}ListNode* del = pcur->next;free(del);pcur->next = NULL;del = NULL;}
}void SLProntPop(ListNode** ptr)
{assert(ptr && *ptr);ListNode* del = *ptr;*ptr = (*ptr)->next;free(del);del = NULL;
}//ListNode* SLFind(ListNode** ptr, datatype x)
//{
// assert(ptr && *ptr);
// ListNode* pcur = *ptr;
// while (pcur)
// {
// if (pcur->data == x)
// {
// return pcur;
// }
// pcur = pcur->next;
// }
// return NULL;
//}void SLPosPush(ListNode** ptr, ListNode* pos, datatype x)
{assert(ptr && *ptr && pos);if (pos == *ptr){SLProntPush(ptr, x);}else{ListNode* node = buynode(x);ListNode* pcur = *ptr;while (pcur->next != pos){pcur = pcur->next;}node->next = pos;pcur->next = node;}
}void SLPosPop(ListNode** ptr, ListNode* pos)
{assert(ptr&&*ptr&&pos);if (pos == *ptr){SLProntPop(ptr);}else{ListNode* pcur = *ptr;while (pcur->next != pos){pcur = pcur->next;}pcur->next = pos->next;free(pos);pos = NULL;}
}void SLDesTory(ListNode** ptr)
{assert(ptr);ListNode* pcur = *ptr;while (pcur){ListNode* del = pcur->next;pcur = pcur->next;free(pcur);pcur = del;}pcur = NULL;*ptr = NULL;
}
contact.c
#include"单链表contact.h"
#include"ListNode.h"void ConAdd(contact** ptr)
{assert(ptr);printf("请输入添加的联系人的姓名 性别 年龄 住址 电话\n");person pes;scanf("%s", pes.name);scanf("%s", pes.gender);scanf("%d", &pes.age);scanf("%s", pes.addr);scanf("%s", pes.num);SLBackPush(ptr, pes);
}void ConPrint(contact** ptr)
{assert(ptr);printf("姓名 性别 年龄 住址 电话\n");contact* pcur = *ptr;while (pcur){printf("%s %s %d %s %s\n", pcur->data.name,pcur->data.gender,pcur->data.age,pcur->data.addr,pcur->data.num);pcur = pcur->next;}
}void ConPop(contact** ptr)
{assert(ptr && *ptr);char NAME[NAME_MAX];printf("请输入你要删除的联系人的姓名:\n");scanf("%s", NAME);contact* pcur = *ptr;while (pcur){if (strcmp(pcur->data.name,NAME)==0){if (pcur==*ptr){pcur = pcur->next;free(*ptr);*ptr = pcur;break;}else{contact* prev = *ptr;while (prev->next != pcur){prev = prev->next;}prev->next = pcur->next;free(pcur);pcur = NULL;break;}}pcur = pcur->next;}
}void ConFind(contact** ptr)
{assert(ptr && *ptr);char NAME[NAME_MAX];printf("请输入你要查找的联系人的姓名:\n");scanf("%s", NAME);contact* pcur = *ptr;while (pcur){if (strcmp(pcur->data.name, NAME) == 0){printf("姓名 性别 年龄 住址 电话\n");printf("%s %s %d %s %s\n", pcur->data.name,pcur->data.gender,pcur->data.age,pcur->data.addr,pcur->data.num);}pcur = pcur->next;}
}void ConGai(contact** ptr)
{assert(ptr && *ptr);char NAME[NAME_MAX];printf("请输入你要修改的联系人的姓名:\n");scanf("%s", NAME);contact* pcur = *ptr;while (pcur){if (strcmp(pcur->data.name, NAME) == 0){printf("请输入修改后的联系人的姓名 性别 年龄 住址 电话\n");scanf("%s %s %d %s %s",pcur->data.name,pcur->data.gender,&pcur->data.age,pcur->data.addr,pcur->data.num);}pcur = pcur->next;}
}void ConDesTroy(contact** ptr)
{assert(ptr && *ptr);contact* pcur = *ptr;while (pcur){contact* del = pcur->next;free(pcur);pcur = del;}pcur = NULL;
}
最终实现的形式如下
相关文章:

单链表实现通讯录
之前我们完成了基于顺序表(动态)实现通讯录,现在我们链表学完了,可以尝试着使用链表来实现我们的通讯录。 首先我们要明白我们写的通讯录是由一个个节点组成的,每个节点里存储的就是我们的联系人信息。也就是说 我们需…...
Linux 命令操作技巧
Linux命令行界面提供了丰富的快捷键来提高操作效率,以下是一些常用的Linux终端快捷键,主要基于Bash shell: Tab - 自动补全:输入命令、文件名、目录名或命令选项的开头部分,然后按Tab键,系统会自动补全剩余…...

深度学习21天 —— 卷积神经网络(CNN):识别验证码( 第12天)
目录 一、前期准备 1.1 标签数字化 1.2 加载数据 1.3 配置数据 二、其他 2.1 损失函数 categorical_crossentropy 2.2 plt.legend(loc ) 2.3 history.history 活动地址:CSDN21天学习挑战赛 学习:深度学习100例-卷积神经网络(CNN&…...
利用 Docker 简化Redis部署:快速搭建Redis服务
利用 Docker 简化Redis部署:快速搭建Redis服务 目录 利用 Docker 简化Redis部署:快速搭建Redis服务为什么选择 Docker准备工作拉取Redis镜像快速运行Redis容器验证Redis服务总结 在现代软件开发中,Redis作为一种高性能的键值数据库࿰…...
Web前端框架:深入探索与实践
Web前端框架:深入探索与实践 在当下数字化飞速发展的时代,Web前端框架的选择与应用成为了开发者们关注的焦点。Node.js,作为一种强大的后端技术,在前端框架的构建中也发挥着不可或缺的作用。本文将围绕Node.js Web前端框架&#…...

【算法】贪心算法——柠檬水找零
题解:柠檬水找零(贪心算法) 目录 1.题目2.题解3.参考代码4.证明5.总结 1.题目 题目链接:LINK 2.题解 分情况讨论 贪心算法 当顾客为5元时,收下当顾客为10元时,收下10元并找回5元当顾客为20元时,收下20元并找回10…...

Jmeter安装教程
1 Jmeter下载 Jmeter下载地址:https://jmeter.apache.org/download_jmeter.cgi,选择需要的版本点击下载 解压jmeter安装包 解压后的安装包如下: 2 配置Jmeter环境变量 进入环境变量配置页面:计算机->属性->高级系统设置-&…...
关于磁盘管理
磁盘管理是操作系统提供的一项功能,用于高效地组织、维护和控制计算机的硬盘驱动器及其卷(分区)。通过磁盘管理工具,用户和管理员可以执行多种与存储相关的高级任务,主要包括: 初始化新磁盘: …...

人大金仓数据库大小写不敏感确认
1、图形化确认(管理—其他选项—预设选项) 2、命令行确认 # ksql -p 54321 -U system test # show enable_ci; 查看是否大小写敏感,on表示大小敏感,off表示大小写不敏感,使用某些项目的时候,需要设置数据库大小写不敏感&#…...

【Java】还有人不懂继承?25 个 Case 包教包会
还有人不懂继承?25 个 Case 包教包会 1.Implement single inheritance2.Implement multilevel inheritance3.Implement hierarchical inheritance4.Override a base class method into a derived class5.Demonstrate the protected access specifier6.Create an Stu…...
Qt实现窗口失去焦点抖动功能
一、失去焦点检测 当窗口失去焦点时会发出FocusOut事件,具体实现如下: 首先给窗口安装事件过滤器: this->installEventFilter(this);然后在事件过滤器函数中判断有没有失去焦点 bool MessageDialog::eventFilter(QObject *object, QEve…...

Flink 数据源
原理 在 Flink 中,数据源(Source)是其中一个核心组件,负责从各种来源读取数据供 Flink 程序处理。 Flink 的数据源类型丰富,涵盖了从简单测试到生产环境使用的各种场景。Kafka、Socket、文件和集合是 Flink 中最常见…...
在本地电脑中如何用命令操作远程服务器上的数据库
日常做服务器维护,经常操作的2个事情,一个是备份远程服务器上的数据库到本地电脑,一个是将备份下来的数据库是恢复到本机做测试用。下面以阿里云的mysql为例,看看怎么弄。电脑是win10系统,先打开cmd命令行模式…...

uniApp子组件监听数据的变化的方法之一
props:{//用来接收外界传递过来的数据swiperList:{type:Array,default:[]}}, swiperList:是父组件传递过来的值 通过 watch 监听(在父组件中也同样可以使用,跟VUE的监听数据变化同理) watch:{//监听组件中的数据变化swiperList(ol…...
Python容器化技术的15个Docker实践
今天,我们将一起探索如何利用Docker这一强大的容器化工具,来提升你的Python项目开发、部署效率。通过一系列由浅入深的实践案例,你将学会如何将Python应用装入“小盒子”,让它在任何地方都能轻松运行。 1. Docker入门:…...

QT天气预报项目(写在简历上)
一、ui设计 实现功能:可以搜索不同的城市进行天气的查询,并且显示未来7天内的天气,并绘制出当天的最高气温和最低气温曲线图。 学到的知识: stylesheet界面美化 Json数据解析 HTTP通信get请求 使用事件过滤器绘制温度曲线 多控件处理(利用数组) 代码整合调试能力 二…...

从零到一建设数据中台 - 数据可视化
从零到一建设数据中台(八)- 数据可视化 一、数据可视化大屏 数据可视化是借助于图形化手段,清晰有效地传达与沟通信息。 将一些业务的关键指标通过数据可视化的方式展示到一块或多块LED大屏上,以大屏为主要展示载体的数据可视化设计。 在数据可视化大屏构建过程中,为了…...

一步步实现知乎热榜采集:Scala与Sttp库的应用
背景 在大数据时代,网络爬虫技术发挥着不可或缺的作用。它不仅能够帮助我们快速地获取互联网上的信息,还能处理和分析这些数据,为我们提供深刻的洞察。知乎,作为中国领先的问答社区,汇聚了各行各业的专家和广大用户的…...

Windows和Linux系统部署Docker(2)
目录 一、Linux系统部署docker 前置环境: 1.安装需要的软件包, yum-util 提供yum-config-manager功能 2.添加阿里云 docker-ce 仓库 3.安装docker软件包 4.启动 docker并设置开机自启 5.查看版本: 二、windows系统部署docker 1.查看…...
PyCharm中快速搭建Python虚拟环境的指南
在 PyCharm 中创建一个新的 Python 虚拟环境可以帮助你为不同的项目管理不同的依赖包,避免版本冲突。以下是在 PyCharm 中创建虚拟环境的步骤: 打开或创建一个项目: 如果你还没有打开 PyCharm,首先打开它,然后选择“Open”打开一个…...

大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

Unity UGUI Button事件流程
场景结构 测试代码 public class TestBtn : MonoBehaviour {void Start(){var btn GetComponent<Button>();btn.onClick.AddListener(OnClick);}private void OnClick(){Debug.Log("666");}}当添加事件时 // 实例化一个ButtonClickedEvent的事件 [Formerl…...

WPF八大法则:告别模态窗口卡顿
⚙️ 核心问题:阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程,导致后续逻辑无法执行: var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题:…...

【堆垛策略】设计方法
堆垛策略的设计是积木堆叠系统的核心,直接影响堆叠的稳定性、效率和容错能力。以下是分层次的堆垛策略设计方法,涵盖基础规则、优化算法和容错机制: 1. 基础堆垛规则 (1) 物理稳定性优先 重心原则: 大尺寸/重量积木在下…...

前端开发者常用网站
Can I use网站:一个查询网页技术兼容性的网站 一个查询网页技术兼容性的网站Can I use:Can I use... Support tables for HTML5, CSS3, etc (查询浏览器对HTML5的支持情况) 权威网站:MDN JavaScript权威网站:JavaScript | MDN...
【HarmonyOS 5】鸿蒙中Stage模型与FA模型详解
一、前言 在HarmonyOS 5的应用开发模型中,featureAbility是旧版FA模型(Feature Ability)的用法,Stage模型已采用全新的应用架构,推荐使用组件化的上下文获取方式,而非依赖featureAbility。 FA大概是API7之…...
从零手写Java版本的LSM Tree (一):LSM Tree 概述
🔥 推荐一个高质量的Java LSM Tree开源项目! https://github.com/brianxiadong/java-lsm-tree java-lsm-tree 是一个从零实现的Log-Structured Merge Tree,专为高并发写入场景设计。 核心亮点: ⚡ 极致性能:写入速度超…...