单链表实现通讯录
之前我们完成了基于顺序表(动态)实现通讯录,现在我们链表学完了,可以尝试着使用链表来实现我们的通讯录。
首先我们要明白我们写的通讯录是由一个个节点组成的,每个节点里存储的就是我们的联系人信息。也就是说 我们需要先写一个单链表,完成单链表的插入,删除等功能。然后在单链表的基础上将单链表中的每个节点存储的数据改为一个联系人 由于这一部分我们之前写过,所以我在这里就不重复写了,大家可以看下我前面的博客。
一、通讯录的具体形式

二、实现通讯录
(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”打开一个…...
Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
区块链技术概述
区块链技术是一种去中心化、分布式账本技术,通过密码学、共识机制和智能合约等核心组件,实现数据不可篡改、透明可追溯的系统。 一、核心技术 1. 去中心化 特点:数据存储在网络中的多个节点(计算机),而非…...
第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10+pip3.10)
第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10pip3.10) 一:前言二:安装编译依赖二:安装Python3.10三:安装PIP3.10四:安装Paddlepaddle基础框架4.1…...
Linux 下 DMA 内存映射浅析
序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存,但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程,可以参考这篇文章,我觉得写的非常…...
