【数据结构】4.单链表实现通讯录
在上一篇文章我们学会了用单链表来实现各种方法,在这一篇文章我们将在单链表的基础上实现通讯录。
0、准备工作
实现通讯录之前,我们还需要在单链表的基础上添加2个文件,头文件Contact.h和源文件Contact.c。Contact.c来实现通讯录方法的声明,而Contact.h来实现通讯录的具体方法。
通讯录的数据其实就是存储在单链表上的,只不过变成了一个结构体,因此我们需要在Contact.h定义一个联系人数据的结构体。
如下图所示:

代码如下:
typedef struct PersonInfo
{char name[NAME_MAX];char gender[GENDER_MAX];int age;char tel[TEL_MAX];char addr[ADDR_MAX];
}PeoInfo;
为了代码有更好的延展性,我们还可以对数组的内存大小进行宏定义,在Contact.h的头部定义:
#define NAME_MAX 10
#define GENDER_MAX 10
#define TEL_MAX 20
#define ADDR_MAX 100
由于我们是再单链表的基础上进行的,所以我们可以对单链表重新起个名字叫做通讯录,在Contact.h中定义:typedef struct SListNode contact;
由于我们是将类型由整型类型改为结构体类型,那么我们还需要再SList,h中将类型重定义一下:typedef struct PersonInfo SLDataType;
接着就可以在Contact.h中声明一系列的方法:
//通讯录的初始化
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);//读取文件内容到通讯录
void LoadContact(contact** con);//保存通讯录数据到文件
void SaveContact(contact** con);
接下来再在Contact.c进行方法的实现,在实现之前我们需要包含以下头文件:
#include"Contact.h"
#include"SList.h"
1、初始化通讯录
初始化代码:
void InitContact(contact** con)
{LoadContact(con);
}
通讯录的初始化实际上就是将文件的数据导入到通讯录,确保通讯录一开始就拥有之前的数据,而不是一个空壳。
将文件数据导入到通讯录代码如下:
void LoadContact(contact** con)
{//打开文件FILE* pf = fopen("contact.txt", "rb");if (pf == NULL){perror("fopen fail!");return;}//定义通讯录变量PeoInfo info;//依次读取文件数据while (fread(&info, sizeof(info), 1, pf)){//将文件数据尾插到通讯录中SLTPushBack(con, info);}printf("历史通讯录数据导入成功!\n");//有开有闭fclose(pf);
}
2、添加通讯录数据
思路:创建通讯录局部变量,输入对应信息后,再尾插到通讯录中。
void AddContact(contact** con)
{PeoInfo info;printf("请输入要添加的联系人姓名:");scanf("%s", info.name);printf("请输入要添加的联系人性别:");scanf("%s", info.gender);printf("请输入要添加的联系人年龄:");scanf("%d", &info.age);printf("请输入要添加的联系人电话:");scanf("%s", info.tel);printf("请输入要添加的联系人地址:");scanf("%s", info.addr);SLTPushBack(con, info);printf("添加联系人成功!\n");
}
再进行测试:
int main()
{contact* con=NULL;AddContact(&con);return 0;
}
运行结果:

我们再进行调试观察是否真正的插入成功:

可以观察到确实成功插入数据了!
3、展示通讯录数据
为了方便我们观察方法是否成功进行,我们可以先编写展示通讯录数据的方法。
思路:先打印表头,再创建指针cur指向头结点,遍历整个通讯录,依次打印对应的信息。
void ShowContact(contact* con)
{printf("%-10s %-4s %-4s %-11s %-20s\n", "姓名", "性别", "年龄", "电话", "地址");contact* cur = con;while (cur){printf("%-10s %-4s %4s %-11s %-20s\n", cur->data.name,cur->data.gender,cur->data.age,cur->data.tel,cur->data.addr);cur = cur->next;}
}
再进行测试:
int main()
{contact* con=NULL;AddContact(&con);ShowContact(con);return 0;
}
通讯录数据显示成功!
4、删除通讯录数据
在进行删除,修改等一系列操作的时候,我们首先需要知道删除或修改的对象是谁,因此还需要一个函数专门根据姓名来查找到联系人,再进行后续的一系列操作。
查找思路:传一个头结点,和要查找的姓名,先创建指针指向头结点,再遍历通讯录,看要查找的名字是否在通讯录中,在就返回下标,不在就返回空。
contact* FindByName(contact* con, char name[])
{contact* cur = con;while (cur){if (0 == strcmp(name, cur->data.name)){return cur;}cur = cur->next;}//没找到返回NULLreturn NULL;
}
接着就可以删除通讯录数据了。
思路:输入要查找的姓名,调用查找函数找到下标,如果下标为空就表示没有找到,并且终止程序,下标不为空就执行删除指定位置数据的操作。
void DelContact(contact** con)
{char name[NAME_MAX];printf("请输入要删除的联系人姓名:");scanf("%s", name);contact* pos = FindByName(*con, name);if (pos == NULL){printf("你要删除的联系人不存在!\n");return;}SLTErase(con, pos);printf("删除成功!\n");
}
再进行测试:
int main()
{contact* con=NULL;AddContact(&con);AddContact(&con);ShowContact(con);DelContact(&con);ShowContact(con);return 0;
}
运行结果:

我们可以观察到删除成功!
5、查找通讯录数据
思路:输入要查找的姓名,调用查找函数找到下标,下标为空就没找到并终止程序,不为空就直接展示下标所对应的数据。
void FindContact(contact** con)
{char name[NAME_MAX];printf("请输入要查找的联系人姓名:");scanf("%s", name);contact* pos = FindByName(*con, name);if (pos == NULL){printf("你要查找的联系人不存在!\n");return;}printf("查找成功!\n");//打印下标对应的数据printf("%-10s %-4s %-4s %-11s %-20s\n", "姓名", "性别", "年龄", "电话", "地址");printf("%-10s %-4s %-4d %-11s %-20s\n",pos->data.name,pos->data.gender,pos->data.age,pos->data.tel,pos->data.addr);
}
再进行测试:
int main()
{contact* con=NULL;AddContact(&con);AddContact(&con);FindContact(&con);return 0;
}
运行结果:

6、修改通讯录数据
思路:输入要查找的姓名,调用查找函数找到下标,下标为空就没找到并终止程序,不为空就直接修改下标所对应的数据。
void ModifyContact(contact** con)
{char name[NAME_MAX];printf("请输入要修改的联系人姓名:");scanf("%s", name);contact* pos = FindByName(*con, name);if (pos == NULL){printf("你要修改的联系人不存在!\n");return;}//修改下标对应的数据printf("请输入要修改的姓名:");scanf("%s", pos->data.name);printf("请输入要修改的性别:");scanf("%s", pos->data.gender);printf("请输入要修改的年龄:");scanf("%d", &pos->data.age);printf("请输入要修改的电话:");scanf("%s", pos->data.tel);printf("请输入要修改的地址:");scanf("%s", pos->data.addr);printf("修改成功!\n");
}
再进行测试:
int main()
{contact* con=NULL;AddContact(&con);ShowContact(con);ModifyContact(&con);ShowContact(con);return 0;
}
运行结果:

修改成功!
7、保存通讯录数据
在我们实现以上方法后,我们输入数据之后可以将其保存,下一次再直接导入使用,因此还需要写一个保存数据的函数。
思路:打开文件,创建指针cur指向头结点,遍历单链表,依次将cur对应的联系人数据写入文件中。
void SaveContact(contact* con)
{FILE* pf = fopen("contact.txt", "wb");if (pf == NULL){perror("fopen fail!");return;}contact* cur = con;while (cur){//将当前节点对应的联系人数据写入文件fwrite(&(cur->data), sizeof(PeoInfo), 1, pf);cur = cur->next;}printf("保存成功!\n");fclose(pf);
}
我们再进行测试:
int main()
{contact* con=NULL;AddContact(&con);SaveContact(con);return 0;
}
可以观察到,数据已经成功被保存到文件中了!
8、销毁通讯录
思路:先保存通讯录数据,再调用单链表的销毁函数。
void DestroyContact(contact** con)
{SaveContact(*con);SListDestroy(con);
}
再进行测试:
int main()
{contact* con = NULL;AddContact(&con);DestroyContact(&con);return 0;
}

可以观察到数据已经成功销毁了,同时数据已经被存储到文件中了。
9、通讯录的完整实现
再实现了上述方法之后,我们就可以在test.c中编写可视化的操作页面,通过调用通讯录的方法来完整实现通讯录了。
思路:先编写主界面函数,主函数:输入值先赋值为-1,通讯录初始化,再使用do while循环条件输入0终止,调用主界面,输入值,根据输入的值使用switch来执行对应的操作,最后再将通讯录销毁。
void menu()
{printf("****************** 通讯录 ******************\n");printf("********* 1、增加联系人 2、删除联系人 **************\n");printf("********* 3、查找联系人 4、修改联系人 **************\n");printf("********* 5、展示联系人 6、保存联系人 **************\n");printf("********* 0、退出通讯录 **************\n");
}int main()
{int input = -1;contact* con = NULL;InitContact(&con);do{menu();printf("请选择你的操作:");scanf("%d", &input);switch (input){case 1:AddContact(&con);break;case 2:DelContact(&con);break;case 3:FindContact(&con);break;case 4:ModifyContact(&con);break;case 5:ShowContact(con);break;case 6:SaveContact(con);break;case 0:printf("退出通讯录...\n");break;default:printf("输入错误,请重新选择你的操作:\n");break;}} while (input!=0);DestroyContact(&con);return 0;
}
再进行测试:

发现已经可以实现完整的通讯录功能了。
点击在gitee查看完整源代码
相关文章:
【数据结构】4.单链表实现通讯录
在上一篇文章我们学会了用单链表来实现各种方法,在这一篇文章我们将在单链表的基础上实现通讯录。 0、准备工作 实现通讯录之前,我们还需要在单链表的基础上添加2个文件,头文件Contact.h和源文件Contact.c。Contact.c来实现通讯录方法的声明…...
接口自动化测试(一)
一、HTTP请求的核心概念及原理详解 HTML:超文本标记语言-----通过<标记符>内容</标记符>格式-------页面 URL:统一资源定位符 返回数据有很多:页面、图片、视频,都可以进行返回---统称为:资源HTTP:超文本传输协议(请求-响应的协…...
【JavaEE】Spring AOP的注解实现
目录 一、AOP 与 Spring AOP二、Spring AOP简单实现三、详解Spring AOP3.1 Spring AOP 核心概念3.1.1 切点(Pointcut)3.1.2 连接点(Join Point)3.1.3 通知(Advice)3.1.4 切面(Aspect)…...
从零开始实现 MobileViT 注意力机制——轻量级Transformer Vision Model 的新思路
从零开始实现 MobileViT 注意力机制——轻量级Transformer Vision Model 的新思路 近年来,计算机视觉领域中 Transformer 模型的崛起为图像处理带来了新的活力。特别是在 ViT(Vision Transformer)模型提出之后,Transformer 在图像…...
揭秘大数据 | 22、软件定义存储
揭秘大数据 | 19、软件定义的世界-CSDN博客 揭秘大数据 | 20、软件定义数据中心-CSDN博客 揭秘大数据 | 21、软件定义计算-CSDN博客 老规矩,先把这个小系列的前三篇奉上。今天书接上文,接着叙软件定义存储的那些事儿。 软件定义存储源于VMware公司于…...
OpenCV 图形API(37)图像滤波-----分离过滤器函数sepFilter()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 应用一个可分离的线性滤波器到一个矩阵(图像)。 该函数对矩阵应用一个可分离的线性滤波器。也就是说,首先&a…...
flutter下载SDK环境配置步骤详解
目录 1.Flutter官网地址、SDK下载地址? 1.1 选择你电脑的系统 2.配置环境 3.解决环境报错 zsh:command not found:flutter 1.Flutter官网地址、SDK下载地址? flutter官网地址: URL 1.1 选择你电脑的系统 下载解压动目录就OK了 2.配置环境 1、打开命令行…...
数据结构与算法入门 Day 0:程序世界的基石与密码
🌟数据结构与算法入门 Day 0:程序世界的基石与密码🔑 ps:接受到了不少的私信反馈,说应该先把前置的知识内容做一个梳理,所以把昨天的文章删除了,重新开启今天的博文写作 Hey 小伙伴们ÿ…...
vscode终端运行windows服务器的conda出错
远程windows服务器可以运行,本地vscode不能。 打开vscode settings.json文件 添加conda所在路径...
Elasticsearch 查询排序报错总结
Elasticsearch 查询sort报错总结 文章目录 Elasticsearch 查询`sort`报错总结错误1、使用Es对 `sort` 进行排序字段类型的要求1.1、数值类型(如 `integer`、`long`、`float`、`double`)1.2、日期类型(如 `date`)1.3、字符串类型(如 `keyword`、`text`)1.4、布尔类型(`bo…...
“大湾区珠宝艺境花园”璀璨绽放第五届消博会
2025年4月13日,第五届中国国际消费品博览会(以下简称"消博会")重要主题活动——《大湾区珠宝艺境花园》启动仪式在海南国际会展中心2号馆隆重举行。由广东省金银珠宝玉器业厂商会组织带领粤港澳大湾区优秀珠宝品牌,以“…...
十、自动化函数+实战
Maven环境配置 1.设计测试用例 2.创建空项目 1)添加需要的依赖pom.xml <dependencies> <!-- 截图配置--><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</…...
Day09【基于jieba分词和RNN实现的简单中文分词】
基于jieba分词和RNN实现的中文分词 目标数据准备主程序预测效果 目标 本文基于给定的中文词表,将输入的文本基于jieba分词分割为若干个词,词的末尾对应的标签为1,中间部分对应的标签为0,同时将分词后的单词基于中文词表做初步序列…...
自动化测试——selenium
简介 Selenium 是一个广泛使用的自动化测试工具,主要用于 Web 应用程序的自动化测试。它能实现的功能是网页的自动化操作,例如自动抢票刷课等。同时你应该也见到过有些网站在打开之后并没有直接加载出网站的所有内容,比如一些图片等等&#x…...
java和python实现mqtt
说明: MQTT 异步通信系统功能文档 系统概述 本系统基于 MQTT 协议实现异步通信,包含三个核心组件: Broker(消息代理):负责消息的路由和转发。 Client(主客户端):定时发…...
5.9 《GPT-4调试+测试金字塔:构建高可靠系统的5大实战策略》
5.4 测试与调试:构建企业级质量的保障体系 关键词:测试金字塔模型、GPT-4调试助手、LangChain调试模式、异步任务验证 测试策略设计(测试金字塔实践) #mermaid-svg-RblGbJVMnCIShiCW {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill…...
Linux——进程通信
我们知道,进程具有独立性,各进程之间互不干扰,但我们为什么还要让其联系,建立通信呢?比如:数据传输,资源共享,通知某个事件,或控制某个进程。因此,让进程间建…...
学习笔记十三—— 理解 Rust 闭包:从语法到 impl Fn vs Box<dyn Fn>
🧠 理解 Rust 闭包:从语法到 impl Fn vs Box 📚 目录 闭包是什么?和普通函数有什么不同?闭包的语法长什么样?闭包“捕获变量”是什么意思?闭包和所有权的关系Fn、FnMut、FnOnce 三种闭包类型的…...
【免费参会合集】2025年生物制药行业展会会议表格整理
全文精心整理, 建议今年参会前都好好收藏着,记得点赞! 医药人非常吃资源,资源从何而来?作为一名从事医药行业的工作者,可以很负责任的告诉诸位,其中非常重要的一个渠道就是会议会展! 建议所有医…...
腾讯云开发+MCP:旅游规划攻略
1.登录注册好之后进入腾讯云开发 2.创建环境 4.创建好环境之后点击去开发 5.进入控制台后,选择AI,找到MCP 6.点击创建MCP Server 使用腾讯云开发创建MCP目前需要云开发入门版99/月,我没开通,所以没办法往下进行。...
银河麒麟系统 达梦8 安装 dlask 框架后端环境
适配的一套环境为 dmPython2.5.8 dmSQLAlchemy1.4.39 Flask2.0.3 Flask-Cors3.0.10 Flask-SQLAlchemy2.5.1 SQLAlchemy1.4.54 Werkzeug2.2.2其中 # sqlalchemy-dm1.4.39 通过dmdbms目录内文件进行源码安装 (MindSpore) [ma-user python]$pwd /home/syl/dmdbms/drivers/python…...
Cribl (实验) vpc-flow 数据抽样
先看文档: Firewall Logs: VPC Flow Logs, Cisco ASA, Etc. | Cribl Docs Firewall Logs: VPC Flow Logs, Cisco ASA, Etc. Recipe for Sampling Firewall Logs Firewall logs are another source of important operational (and security) data. Typical examples include Ama…...
Sklearn入门之数据预处理preprocessing
、 Sklearn全称:Scipy-toolkit Learn是 一个基于scipy实现的的开源机器学习库。它提供了大量的算法和工具,用于数据挖掘和数据分析,包括分类、回归、聚类等多种任务。本文我将带你了解并入门Sklearn下的preprocessing在机器学习中的基本用法。 获取方式…...
我想自己组装一台服务器,微调大模型通义千问2.5 Omni 72B,但是我是个人购买,资金非常有限,最省的方案
目录 🧠 首先我们要搞清楚几个核心点: 🎯 目标:微调 Qwen2.5-Omni-72B 🚨 现实问题:作为个人用户,72B 模型几乎无法负担全量微调 💸 全量微调硬件需求: ✅ 最省的个人方案:不组 72B,只训练 Qwen2.5-Omni-7B 或 14B 💡 推荐方案 A:个人桌面级多卡训练服…...
家用打印机性价比排名及推荐
文章目录 品牌性价比一、核心参数对比与场景适配二、技术类型深度解析三、不同场景选择 相关文章 品牌 性价比 一、核心参数对比与场景适配 兄弟T436W 优势: 微压电技术,打印头寿命长,堵头率低。 支持A4无边距和5G WiFi,适合照片…...
KWDB(Knowledge Worker Database)基础概念与原理完整指南
KWDB(Knowledge Worker Database)基础概念与原理完整指南—目录 前言一、背景1.1 知识工作者的痛点1.2 技术演进推动 二、定义与定位2.1 什么是KWDB?2.2 KWDB与传统数据库的对比与传统关系型数据库(如MySQL)的对比与分…...
数字电子技术基础(四十七)——使用Mutlisim软件来模拟74LS85芯片
目录 1 使用74LS85N芯片完成四位二进制数的比较 1.1原理介绍 1.2 器件选择 1.3 运行电路 2 使用74LS85N完成更多位的二进制比较 1 使用74LS85N芯片完成四位二进制数的比较 1.1原理介绍 对于74LS85 是一款 4 位数值比较器集成电路,用于比较两个 4 位二进制数&…...
关于STM32创建工程文件启动文件选择
注意启动文件只要选择这几个 而不是要把所有都选上...
LLC电路工作在容性区的风险
在t0时刻之前,Q6Q7导通,回路如下所示,此时A点电压是低压,B点电压是高压 在t0时刻时,谐振电流相位发生变换,在t1时刻,Q5,Q8导通,对于Q8MOS管来说,B点电压在Q6Q…...
Linux Kernel 6
clone 系统调用(The clone system call) 在 Linux 中,使用 clone() 系统调用来创建新的线程或进程。fork() 系统调用和 pthread_create() 函数都基于 clone() 的实现。 clone() 系统调用允许调用者决定哪些资源应该与父进程共享,…...
