通讯录(基于单链表)

embedded/2024/9/24 20:13:07/

通讯录(基于单链表)

我们知道 链表是由一个个节点组成的,我们让节点的数据域去存储一个结构体

这个结构体是存储联络人数据的一个结构体,里面放着许多信息:

// 要在链表的每一个节点中存储联系人数据 
// 那我们就要定义联系人的结构体
#define NAME_MAX 20
#define GENDER_MAX 10
#define TEL_MAX 20
#define ADDR_MAX 100typedef struct personInfo
{char name[NAME_MAX]; // 名字char gender[GENDER_MAX]; // 性别int age; // 年龄char tel[TEL_MAX]; // 电话char addr[ADDR_MAX]; // 地址
}peoInfo;

由于有了前面单链表的知识 我们这边直接放代码:

代码:

我们一共分为五个文件来完成这个通讯录,分别是之前单链表的SList.c,SList.h,

test.c(测试文件),Contact.h(通讯录头文件),Contact.c(通讯录功能实现)

SList.c:

# include"SList.h"
// 链表的功能函数实现都在这里// 链表节点的申请
ListNode* SLTBuyNode(SLTDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("malloc");return;}newnode->data = x;newnode->next = NULL;return newnode;
} 链表的打印
//void SLTPrint(ListNode* phead)
//{
//	// 遍历链表
//	ListNode* pcur = phead;
//	while (pcur)
//	{
//		printf("%d->", pcur->data);
//		pcur = pcur->next;
//	}
//	printf("NULL\n");
//}// 链表的尾插
void SLTPushBack(ListNode** pphead, SLTDataType x)
{assert(pphead);ListNode* newnode = SLTBuyNode(x); //申请节点if (*pphead == NULL){*pphead = newnode;}else{ListNode* pcur = *pphead;while (pcur){if (pcur->next == NULL){// 尾插pcur->next = newnode;break;}pcur = pcur->next;}}
}// 链表的头插
void SLTPushFront(ListNode** pphead, SLTDataType x)
{assert(pphead);ListNode* newnode = SLTBuyNode(x); // 申请节点// 头插ListNode* pcur = *pphead;newnode->next = pcur;*pphead = newnode;
}// 链表的尾删
void SLTPopBack(ListNode** pphead)
{assert(pphead);assert(*pphead);if ((*pphead)->next == NULL) // 只有一个节点{free(*pphead);*pphead = NULL;}else{ListNode* pcur = *pphead;ListNode* prev = *pphead;// 遍历链表找到尾节点while (pcur->next != NULL){prev = pcur;// prev指向pcur的前一个节点pcur = pcur->next;}// 走到这里说明pcur指向最后一个节点prev->next = NULL;free(pcur);pcur = NULL;}
}// 链表的头删
void SLTPopFront(ListNode** pphead)
{assert(pphead);assert(*pphead);//头删ListNode* next = (*pphead)->next;free(*pphead);*pphead = next;
} 链表的查找
//ListNode* SLTFind(ListNode* phead, SLTDataType x)
//{
//	assert(phead);
//
//	ListNode* pcur = phead;
//	// 遍历链表 查找
//	while (pcur)
//	{
//		if (pcur->data == x)
//		{
//			return pcur;
//		}
//		pcur = pcur->next;
//	}
//	// 走到这里说明没找到
//	return NULL;
//}// 在链表的指定位置之前插入数据
void SLTInsert(ListNode** pphead, ListNode* pos, SLTDataType x)
{assert(pphead);assert(*pphead);assert(pos);// 申请节点ListNode* newnode = SLTBuyNode(x);// 要分成头插 和其他情况if (pos == *pphead){SLTPushFront(pphead, x); // 引用头插}else{ListNode* pcur = *pphead;ListNode* prev = *pphead;while (pcur != pos){if (pcur == NULL){printf("不存在pos这个节点,请重新输入合法的值\n");return;}prev = pcur;// prev 指向pcur的前一个节点pcur = pcur->next;}// 走到这里说明pcur指向的就是pos节点// 完成链接 prev->newnode->posprev->next = newnode;newnode->next = pos;}
}// 在链表指定位置之后插入数据
void SLTInsertAfter(ListNode* pos, SLTDataType x) // 不需要改变第一个节点的位置 无需传入第一个节点的指针
{assert(pos);// 申请节点ListNode* newnode = SLTBuyNode(x);// 尾插  // 完成 pos->newnode->pos的下一个节点的链接newnode->next = pos->next;pos->next = newnode;
}// 删除链表指定位置的节点
void SLTErase(ListNode** pphead, ListNode* pos)
{assert(pphead);assert(*pphead);assert(pos);// 要分成第一个节点 和其他情况 if (pos == *pphead){// 头删SLTPopFront(pphead);}else{ListNode* pcur = *pphead;ListNode* prev = *pphead;// 遍历链表 找到pos节点while (pcur != pos){if (pcur == NULL){printf("没有找到pos节点,请重新输入合法值\n");return;}prev = pcur;pcur = pcur->next;}// 走到这里说明pcur指向的就是pos节点prev->next = pcur->next;free(pcur);pcur = NULL;}
}// 删除链表指定位置之后的节点
void SLTEraseAfter(ListNode* pos) // 不需要改变第一个节点的位置 无需传入**pphead
{assert(pos);assert(pos->next); // pos的下一个节点不能没有 不然怎么删除// 删除  链接 pos节点 和 pos的下个节点所存储的next指针ListNode* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;// 下面这段代码 也可以实现删除pos之后的节点的功能//SLTNode* next = pos->next->next;//free(pos->next);//pos->next = NULL;//pos->next = next;
}//  销毁链表
void SListDesTroy(ListNode** pphead)
{assert(pphead);assert(*pphead);ListNode* pcur = *pphead;ListNode* next = NULL;// 遍历链表 一个节点一个节点的销毁while (pcur){next = pcur->next;free(pcur);pcur = next;}// 走到这里说明删除完了*pphead = NULL;
}

SList.h

#pragma once
#define  _CRT_SECURE_NO_WARNINGS 1
# include<stdio.h>
# include<stdlib.h>
# include<assert.h>
# include<string.h>
# include"Contact.h"// 节点的结构体定义
typedef peoInfo SLTDataType;
typedef struct ListNode
{SLTDataType data;struct ListNode* next;
}ListNode;// 节点的申请
ListNode* SLTBuyNode(SLTDataType x);// 链表的打印
void SLTPrint(ListNode* phead);//头部插⼊删除/尾部插⼊删除
void SLTPushBack(ListNode** pphead, SLTDataType x);
void SLTPushFront(ListNode** pphead, SLTDataType x);
void SLTPopBack(ListNode** pphead);
void SLTPopFront(ListNode** pphead);//查找
ListNode* SLTFind(ListNode* phead, SLTDataType x);//在指定位置之前插入数据
void SLTInsert(ListNode** pphead, ListNode* pos, SLTDataType x);//在指定位置之后插入数据
void SLTInsertAfter(ListNode* pos, SLTDataType x); // 不需要改变第一个节点的位置 无需传入第一个节点的指针//删除pos节点
void SLTErase(ListNode** pphead, ListNode* pos);//删除pos之后的节点
void SLTEraseAfter(ListNode* pos); // 不需要改变第一个节点的位置 无需传入**pphead//销毁链表
void SListDesTroy(ListNode** pphead);

Contact.h:

#pragma once// 要在链表的每一个节点中存储联系人数据 
// 那我们就要定义联系人的结构体
#define NAME_MAX 20
#define GENDER_MAX 10
#define TEL_MAX 20
#define ADDR_MAX 100typedef struct personInfo
{char name[NAME_MAX]; // 名字char gender[GENDER_MAX]; // 性别int age; // 年龄char tel[TEL_MAX]; // 电话char addr[ADDR_MAX]; // 地址
}peoInfo;//前置声明 
typedef struct ListNode contact; // 将链表改个名字 改成通讯录的名字//初始化通讯录
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 "SList.h"
# include "Contact.h"
// 通讯录的功能函数实现都在这里// 从文件中读取联系人数据到通讯录
void LoadContact(contact** con)
{// 打开文件FILE* pf = fopen("contact.txt", "rb");if (pf == NULL){perror("fopen");exit(1);}// 开始读取数据peoInfo info;// while循环每次从pf中拿出一个联络人数据放到info中 while (fread(&info, sizeof(peoInfo), 1, pf)){// 将info尾插到链表中  也就是放到通讯录中SLTPushBack(con, info);}// 关闭文件fclose(pf);pf = NULL;
}// 初始化通讯录
void InitContact(contact** con)
{LoadContact(con); // 从文件中读取联络人数据到通讯录中
}//展示通讯录数据
void PenInfoPrint(peoInfo x)
{printf("%s %s %d %s %s\n", x.name, x.gender, x.age, x.tel, x.addr);
}
void ShowContact(contact* con)
{contact* cur = con;printf("该通讯录的联系人信息如下:\n");printf("姓名  性别  年龄  电话  地址\n"); // 表头while (cur){PenInfoPrint(cur->data);cur = cur->next;}
}//添加通讯录数据
void AddContact(contact** con) // 不能传未初始化的con进来
{peoInfo info;// 输入联络人数据printf("请输入联络人的姓名:\n");scanf("%s", info.name);printf("请输入联络人的性别:\n");scanf("%s", info.gender);printf("请输入联络人的年龄:\n");scanf("%d", &(info.age));printf("请输入联络人的电话:\n");scanf("%s", info.tel);printf("请输入联络人的地址:\n");scanf("%s", info.addr);// 将数据添加到通讯录中SLTPushBack(con, info);printf("插入成功\n");
}//删除通讯录数据
contact* FindByName(contact* con,char name[])
{contact* cur = con;// 遍历通讯录 查看是否有名字相同的while (cur){if (strcmp(cur->data.name, name) == 0){return cur;}cur = cur->next;}//  走到这里说明没找到return NULL;
}
void DelContact(contact** con) //链表的删除指定位置的数据
{// 输入要删除的联络人的姓名char name[NAME_MAX] = { 0 };printf("请选择你要删除的联络人姓名\n");scanf("%s", name);// 我们根据输入的姓名去查找通讯录中是否有该姓名的存在  有就删除 contact* ret = FindByName(*con, name);if (ret != NULL){SLTErase(con, ret);printf("你所要删除的联系人:%s,已经成功删除\n", name);}else{printf("你所要删除的联系人,不存在于通讯录中!!!\n");return;}
}//查找通讯录数据
void FindContact(contact* con)
{// 输入要查找的联络人姓名char name[NAME_MAX] = { 0 };printf("请输入你要查找的联络人姓名\n");scanf("%s", name);contact* ret = FindByName(con, name);if (ret != NULL){printf("你所查找的联络人:%s,信息如下:\n", name);printf("姓名  性别  年龄  电话  地址\n"); // 表头PenInfoPrint(ret->data);}else{printf("你所查找的联络人:%s,不存在!!!\n", name);return;}
}//修改通讯录数据
void ModifyContact(contact** con)
{char name[NAME_MAX] = { 0 };printf("请输入你要修改的联络人姓名\n");scanf("%s", name);contact* ret = FindByName(*con, name);if (ret == NULL){printf("你所查找的联络人:%s,不在通讯录中\n", name);return;}else{// 找到了printf("该联系人的信息如下:\n");printf("姓名  性别  年龄  电话  地址\n"); // 表头PenInfoPrint(ret->data);// 修改 让用户自主选择修改那一部分int n = 0;do {printf("请选择你需要修改的数据\n");printf("*****************************************\n");printf("****1.姓名*******2.性别******3.年龄*******\n");printf("****4.电话*******5.地址******0.退出*******\n");printf("*****************************************\n");scanf("%d", &n);switch (n){case 1:printf("请输入新的姓名\n");scanf("%s", ret->data.name);break;case 2:printf("请输入新的性别\n");scanf("%s", ret->data.gender);break;case 3:printf("请输入新的年龄\n");scanf("%d", &(ret->data.age));break;case 4:printf("请输入新的电话\n");scanf("%s", ret->data.tel);break;case 5:printf("请输入新的地址\n");scanf("%s", ret->data.addr);break;case 0:printf("已退出修改\n");break;default:printf("输入不合法,请重新输入\n");break;}} while (n);printf("修改成功\n");}
}// 将通讯录数据写入文件中
void SaveContact(contact* con)
{// 打开文件FILE* pf = fopen("contact.txt", "wb");if (pf == NULL){perror("fopen");exit(1);}// 将通讯录数据写入文件中// 遍历通讯录contact* cur = con;while (cur){fwrite(&(cur->data), sizeof(peoInfo), 1, pf);cur = cur->next;}printf("成功保存联络人数据!\n");// 关闭文件fclose(pf);pf = NULL;
}//销毁通讯录数据
void DestroyContact(contact** con)
{SListDesTroy(con);
}

test.c:

# include "SList.h"void menu()
{printf("******************通讯录**********************\n");printf("********1.增加联系人   2.删除联系人************\n");printf("********3.修改联系人   4.查找联系人************\n");printf("********5.展示联系人   0.退出******************\n");
}int main()
{//Test01();int n = 0;contact* con = NULL;while (1){int m = 0;printf("是否要导入历史数据?\n");printf("1.yes,2.no\n");scanf("%d", &m);if (m == 1){LoadContact(&con);printf("导入历史数据成功\n");break;}else if (m == 2){printf("已退出......\n");break;}else{printf("输入不合法,请重新输入\n");}}do {menu();printf("请输入你要选择的操作\n");scanf("%d", &n);switch (n){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 (n);while (1){int m = 0;printf("是否要保存联系人数据?\n");printf("1.yes,2.no\n");scanf("%d", &m);if (m == 1){SaveContact(con); // 将联络人信息存储到文件中break;}else if (m == 2){printf("已退出......\n");break;}elseprintf("输入不合法,请重新输入\n");}DestroyContact(&con);// 将通讯录销毁return 0;
}

这里我们优化了一下代码,让用户使用的时候更有体验。


http://www.ppmy.cn/embedded/29557.html

相关文章

Faiss使用指南:5步掌握高效相似性搜索【AI写作助手】

首先&#xff0c;这篇文章是基于笔尖AI写作进行文章创作的&#xff0c;喜欢的宝子&#xff0c;也可以去体验下&#xff0c;解放双手&#xff0c;上班直接摸鱼~ 按照惯例&#xff0c;先介绍下这款笔尖AI写作&#xff0c;宝子也可以直接下滑跳过看正文~ 笔尖Ai写作&#xff1a;…

【讲解下如何解决一些常见的 Composer 错误】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

菜鸡学习netty源码(二)——BootStrap启动

1.概述 上一篇文章我们虽然写的是AbstractBootStrap的一些方法,但是我们主要分析的还是ServerBootStrap的启动,那我这篇文章的话还是继续看AbstractBootStrap的另一个子类BootStrap的启动。 我们先看下netty客户端的demo代码,如下: EventLoopGroup group = new NioEventLo…

Pandas入门篇(三)-------数据可视化篇2(pandas-plot篇)

目录 概述一、格式1. 生成pandas.plotting对象来生成图表2. 调用plot()函数来生成图表3.支持的图表类型 二、单变量绘图常用图表1. 柱状图&#xff08;bar&#xff09;使用场景代码实现 2. 折线图&#xff08;line&#xff09;&#xff08;默认即为折线图&#xff09;适用场景代…

《Redis使用手册之发布与订阅》

《Redis使用手册之发布与订阅》 目录 **《Redis使用手册之发布与订阅》**发布与订阅**SUBSCRIBE&#xff1a;订阅频道****UNSUBSCRIBE&#xff1a;退订频道****PSUBSCRIBE&#xff1a;订阅模式**PUNSUBSCRIBE&#xff1a;退订模式**PUBSUB&#xff1a;查看发布与订阅的相关信息…

golang学习笔记(内存逃逸分析)

golang的内存逃逸 逃逸分析&#xff08; Escape analysis&#xff09; 是指由编译器决定内存分配的位置&#xff0c; 不需要程序员指定。 函数中申请一个新的对象。 如果分配在栈中&#xff0c; 则函数执行结束可自动将内存回收&#xff1b;如果分配在堆中&#xff0c; 则函数…

Neo4j v5 中 Cypher 的变化

How Cypher changed in Neo4j v5 Neo4j v5 中 Cypher 的变化 几周前&#xff0c;Neo4j 5 发布了。如果你像我一样&#xff0c;在 Neo4j 4 的后期版本中忽略了所有的弃用警告&#xff0c;你可能需要更新你的 Cypher 查询以适应最新版本的 Neo4j。幸运的是&#xff0c;新的 Cyp…

自定义 Dockerfile 构建 PostgreSQL 15 编译版 Docker 镜像

BG 前几日 Sean 老师发布了一篇文章 – PostgreSQL安装(一): 再简单点儿&#xff0c;用Docker?, 介绍如何快速安装启动 PostgreSQL 数据库。 本文再稍微延伸一点&#xff0c;介绍一下如何自定义 Dockerfile&#xff0c;加入自己想要预制的参数&#xff0c;构建一个自定义的 …