动态通讯录实现(C语言)

news/2024/11/8 3:06:04/

目录

前言:

一:单个节点的设计和主逻辑 

结点设计

主逻辑

二:接口实现

(1)生成一个新的结点

(2)增加信息

(3)打印信息

(4)查找 

(5)删除信息

(6)修改信息

(7)排序

 插入排序

快速排序

(8)已有数据读取

(9)更新数据录入

三:全部代码

contact.h(声明)

contact.c(接口)

test.c(主逻辑)


前言:

本文使用的存储结构是不带哨兵位链表,还包含了文件操作(读取和录入),有类似课程设计的同学可能需要(完整代码在最后)。

链表(知道基础概念,清楚形参实参关系的可以不看):

https://blog.csdn.net/2301_76269963/article/details/129586021?spm=1001.2014.3001.5502


一:单个节点的设计和主逻辑 

结点设计

需求:一个人要包含姓名、年龄、性别、电话号码、地址这些信息,把这些信息封装成一个结构体。


要让每个人的数据链接成一个整体,节点除了个人信息之外还要保存下个结点的地址


主逻辑

(1)每次开始都从文件中读取数据

(2)通讯录的功能包括:①增加数据

                                     ②依据名字删除数据

                                     ③依据名字查找数据

                                     ④依据名字修改数据

                                     ⑤依据年龄排序数据

                                     ⑥打印数据

(3)退出后重新录入数据


二:接口实现

(1)生成一个新的结点

//生成一个新节点
Node* BuyNewNode()
{Node* NewNode = (Node*)malloc(sizeof(Node));if (NewNode == NULL){printf("malloc error\n");exit(-1);}NewNode->next = NULL;printf("请输入名字:");scanf("%s", NewNode->data.name);printf("请输入年龄:");scanf("%d", &NewNode->data.age);printf("请输入性别:");scanf("%s", NewNode->data.sex);printf("请输入电话:");scanf("%s", NewNode->data.tele);printf("请输入地址:");scanf("%s", NewNode->data.addr);return NewNode;
}

(2)增加信息

//增加信息,可能要更改头指针,传二级指针修改一级指针
void AddContact(Node** pphead)
{//生成新节点Node* NewNode = BuyNewNode();//单独处理空的情况if (*pphead == NULL){*pphead = NewNode;return;}else{NewNode->next = *pphead;*pphead = NewNode;}
}

图解:

(3)打印信息

//打印信息
void PrintContact(Node* phead)
{//注意打印格式,加-表示字符左对齐,数字表示最小字符数(不足补空格)printf("%-20s\t%-5s\t%-5s\t%-20s\t%-20s\n", "名字", "年龄", "性别", "电话", "地址");while (phead != NULL){printf("%-20s\t%-5d\t%-5s\t%-20s\t%-20s\n", phead->data.name,phead->data.age,phead->data.sex,phead->data.tele,phead->data.addr);//迭代phead = phead->next;}
}

(4)查找 

注意:后续的删除和修改都需要用到查找,为增强代码复用性,查找函数返回结点的地址,如果未查找到就返回空。

//查找,第二个参数为名字字符串首地址
Node* FindContact(Node* phead,char* name)
{//遍历链表while (phead){//strcmp()函数用于字符串比较,相同返回0,否则返回非0值if (strcmp(phead->data.name, name) == 0){return phead;}phead = phead->next;}//没找到返回空return NULL;
}

(5)删除信息

先查找,找到待删除结点地址,如果待删除结点为头节点,需要更新头指针

否则就需要找到待删除结点的前一个结点,前一个结点指针域指向待删除结点的下一个结点,然后释放待删除结点。

//删除信息,可能要更改头指针,传二级指针修改一级指针
void DelContact(Node** pphead)
{//信息为空不能删除assert(*pphead != NULL);//查找char name[max_name];printf("请输入要删除联系人的名字:");scanf("%s", name);Node* pos = FindContact(*pphead,name);if (pos == NULL){printf("查找失败\n");return;}//如果pos是第一个节点if (pos == *pphead){//找到下一个Node* NEXT = pos->next;free(pos);*pphead = NEXT;}else{//找到前一个Node* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);}printf("删除成功\n");
}

图解:

(6)修改信息

先查找到目标结点,然后进行修改。

//修改信息
void ModifyContact(Node* phead)
{//查找char name[max_name];printf("请输入要修改联系人的名字:");scanf("%s", name);Node* pos = FindContact(phead, name);if (pos == NULL){printf("查找失败\n");return;}else{printf("找到了,进行修改\n");printf("请输入名字:");scanf("%s", pos->data.name);printf("请输入年龄:");scanf("%d", &pos->data.age);printf("请输入性别:");scanf("%s", pos->data.sex);printf("请输入电话:");scanf("%s", pos->data.tele);printf("请输入地址:");scanf("%s", pos->data.addr);}
}

(7)排序

这里给两种实现方式,第一种使用插入排序(方便理解)第二种使用快速排序(效率更快,有空间消耗,代码量更多)


 插入排序

插入排序的核心思想是:将一个未排序的元素插入到已排序的子序列中,使得插入后依然保持有序。具体实现时,我们可以从第二个元素开始,将其与前面已排序序列的元素比较,找到其正确的插入位置,然后插入即可。在插入过程中,需要将已排序序列中比该元素大的元素后移,为该元素腾出插入位置(看一遍就可以看代码和图解)。

//交换数据
void swap(Node* p1, Node* p2)
{struct PepInfo tmp = p1->data;p1->data = p2->data;p2->data = tmp;
}//进行排序
void sort(Node* phead)
{//空不排序assert(phead != NULL);Node* begin = phead;Node* end = phead->next;while (end){while (begin != end){if (begin->data.age > end->data.age){swap(begin,end);}begin = begin->next;}//更新end = end->next;begin = phead;}
}

图解:


快速排序

这里就不展开讲快速排序的思想了,也不建议用快速排序,一般通讯录这样的数据量用插入排序就足够了,感兴趣的同学可以看链接。

快速排序链接:https://blog.csdn.net/2301_76269963/article/details/130473353?spm=1001.2014.3001.5502

先将链表中的数据拷贝到数组中,对数组进行排序,再把数组数据拷贝回链表。

//交换数据
void swap(PepInfo* p1, PepInfo* p2)
{PepInfo tmp = *p1;*p1 = *p2;*p2 = tmp;
}// 三数取中
int GetMidIndex(PepInfo* a, int left, int right)
{int midi = left + (right - left) / 2;if (a[midi].age > a[right].age){if (a[midi].age < a[left].age)return midi;else if (a[right].age > a[left].age)return right;elsereturn left;}else    //a[right] > a[mid]{if (a[midi].age > a[left].age)return midi;else if (a[left].age < a[right].age)return left;elsereturn right;}
}//前后指针法
int partion3(PepInfo* a, int left, int right)
{int midi = GetMidIndex(a, left, right);swap(&a[midi], &a[left]);int keyi = left;int prev = left;int cur = left + 1;while (cur <= right){//++prev和cur相等,无效交换,不换if (a[cur].age < a[keyi].age && ++prev != cur){swap(&a[prev], &a[cur]);}cur++;}swap(&a[keyi], &a[prev]);return prev;
}//快速排序
void QuickSort(PepInfo* a, int left, int right)
{//如果left>right,区间不存在//left==right,只有一个元素,可以看成是有序的if (left >= right){return;}else{//单次排序int keyi = partion3(a, left, right);//分成左右区间,排序左右区间QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}
}// 链表快速排序函数
void sortList(Node* phead) 
{// 处理空或单节点链表的情况if (!phead || !phead->next){return;}//求链表长度int len = 0;Node* cur = phead;while (cur) {len++;cur = cur->next;}// 将链表转换为数组PepInfo* arr = malloc(len * sizeof(PepInfo));if (arr == NULL){printf("malloc error\n");exit(-1);}cur = phead;for (int i = 0; i < len; i++) {arr[i] = cur->data;cur = cur->next;}// 快速排序QuickSort(arr, 0, len - 1);// 将数组转换为链表cur = phead;for (int i = 0; i < len; i++) {cur->data = arr[i];cur = cur->next;}free(arr);
}

(8)已有数据读取

①生成一个新结点,将读取的数据存入新结点中。

②第一次读取时链表为空,修改头指针。

③后面的新结点链接到链表的尾部。

(注意:读模式打开文件需要文件夹中有这个文件,第一次需要手动创建一个data.txt文件)

//已有数据读取,可能要更改头指针,传二级指针修改一级指针
void StartFileEntry(Node** pphead)
{FILE* pf = fopen("data.txt", "rb");if (pf == NULL){printf("打开文件失败\n");exit(-1);}//保存读取到的数据PepInfo* tmp = (PepInfo*)malloc(sizeof(PepInfo));if (tmp == NULL){printf("malloc error\n");exit(-1);}//先读取一次fread(tmp, sizeof(PepInfo), 1, pf);//保存尾部节点地址Node* tail = NULL;//读取到结尾时feof()返回0,未读取到结尾返回非0值while (!feof(pf)){//生成新节点Node* NewNode = (Node*)malloc(sizeof(Node));if (NewNode == NULL){printf("malloc error\n");exit(-1);}NewNode->data = *tmp;NewNode->next = NULL;//第一次头为空,更新if (*pphead == NULL){*pphead = NewNode;tail = *pphead;}//头不为空,新节点链接到尾后面,尾指针更新else{tail->next = NewNode;tail = tail->next;}//继续读取fread(tmp, sizeof(PepInfo), 1, pf);}fclose(pf);
}

(9)更新数据录入

链表为空,直接返回;否则遍历链表,依次录入数据。

//新数据录入
void UpdateFileEntry(Node* phead)
{FILE* pf = fopen("data.txt", "wb");if (phead == NULL){return;}else{while (phead){fwrite(&(phead->data), sizeof(PepInfo), 1, pf);phead = phead->next;}}fclose(pf);
}

三:全部代码

contact.h(声明)

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
//方便后面更改这些信息的最大容量
#define max_name 20
#define max_tele 20
#define max_addr 20
#define max_sex 10//个人信息
typedef struct PepInfo
{char name[max_name];int age;char sex[max_sex];char tele[max_tele];char addr[max_addr];
}PepInfo;typedef struct Node
{//个人信息struct PepInfo data;//指针域struct Node* next;
}Node;//生成一个新节点
Node* BuyNewNode();//增加信息,可能要更改头指针,传二级指针修改一级指针
void AddContact(Node** pphead);//打印信息
void PrintContact(Node* phead);//查找
Node* FindContact(Node* phead, char* name);//删除信息,可能要更改头指针,传二级指针修改一级指针
void DelContact(Node** pphead);//修改信息
void ModifyContact(Node* phead);//进行排序
// 链表快速排序函数
void sortList(Node* phead);
//插入排序
void sort(Node* phead);//已有数据读取,可能要更改头指针,传二级指针修改一级指针
void StartFileEntry(Node** pphead);//更新数据录入
void UpdateFileEntry(Node* phead);

contact.c(接口)

#define _CRT_SECURE_NO_WARNINGS 1
#include "contact.h"//生成一个新节点
Node* BuyNewNode()
{Node* NewNode = (Node*)malloc(sizeof(Node));if (NewNode == NULL){printf("malloc error\n");exit(-1);}NewNode->next = NULL;printf("请输入名字:");scanf("%s", NewNode->data.name);printf("请输入年龄:");scanf("%d", &NewNode->data.age);printf("请输入性别:");scanf("%s", NewNode->data.sex);printf("请输入电话:");scanf("%s", NewNode->data.tele);printf("请输入地址:");scanf("%s", NewNode->data.addr);return NewNode;
}//增加信息,可能要更改头指针,传二级指针修改一级指针
void AddContact(Node** pphead)
{//生成新节点Node* NewNode = BuyNewNode();//单独处理空的情况if (*pphead == NULL){*pphead = NewNode;return;}else{NewNode->next = *pphead;*pphead = NewNode;}
}//打印信息
void PrintContact(Node* phead)
{//注意打印格式,加-表示字符左对齐,数字表示最小字符数(不足补空格)printf("%-20s\t%-5s\t%-5s\t%-20s\t%-20s\n", "名字", "年龄", "性别", "电话", "地址");while (phead != NULL){printf("%-20s\t%-5d\t%-5s\t%-20s\t%-20s\n", phead->data.name,phead->data.age,phead->data.sex,phead->data.tele,phead->data.addr);//迭代phead = phead->next;}
}//查找,第二个参数为名字字符串首地址
Node* FindContact(Node* phead,char* name)
{//遍历链表while (phead){//strcmp()函数用于字符串比较,相同返回0,否则返回非0值if (strcmp(phead->data.name, name) == 0){return phead;}phead = phead->next;}//没找到返回空return NULL;
}//删除信息,可能要更改头指针,传二级指针修改一级指针
void DelContact(Node** pphead)
{//断言,信息为空不能删除assert(*pphead != NULL);//查找char name[max_name];printf("请输入要删除联系人的名字:");scanf("%s", name);Node* pos = FindContact(*pphead,name);if (pos == NULL){printf("查找失败\n");return;}//如果pos是第一个节点if (pos == *pphead){//找到下一个Node* NEXT = pos->next;free(pos);*pphead = NEXT;}else{//找到前一个Node* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);}printf("删除成功\n");
}//修改信息
void ModifyContact(Node* phead)
{//查找char name[max_name];printf("请输入要修改联系人的名字:");scanf("%s", name);Node* pos = FindContact(phead, name);if (pos == NULL){printf("查找失败\n");return;}else{printf("找到了,进行修改\n");printf("请输入名字:");scanf("%s", pos->data.name);printf("请输入年龄:");scanf("%d", &pos->data.age);printf("请输入性别:");scanf("%s", pos->data.sex);printf("请输入电话:");scanf("%s", pos->data.tele);printf("请输入地址:");scanf("%s", pos->data.addr);}
}交换数据
//void swap(PepInfo* p1, PepInfo* p2)
//{
//	PepInfo tmp = *p1;
//	*p1 = *p2;
//	*p2 = tmp;
//}
//三数取中
//int GetMidIndex(PepInfo* a, int left, int right)
//{
//	int midi = left + (right - left) / 2;
//
//	if (a[midi].age > a[right].age)
//	{
//		if (a[midi].age < a[left].age)
//			return midi;
//		else if (a[right].age > a[left].age)
//			return right;
//		else
//			return left;
//	}
//	else // a[right] > a[mid]
//	{
//		if (a[midi].age > a[left].age)
//			return midi;
//		else if (a[left].age < a[right].age)
//			return left;
//		else
//			return right;
//	}
//}
//
前后指针法
//int partion3(PepInfo* a, int left, int right)
//{
//	int midi = GetMidIndex(a, left, right);
//	swap(&a[midi], &a[left]);
//
//	int keyi = left;
//	int prev = left;
//	int cur = left + 1;
//	while (cur <= right)
//	{
//		//++prev和cur相等,无效交换,不换
//		if (a[cur].age < a[keyi].age && ++prev != cur)
//		{
//			swap(&a[prev], &a[cur]);
//		}
//		cur++;
//	}
//	swap(&a[keyi], &a[prev]);
//	return prev;
//}
//
快速排序
//void QuickSort(PepInfo* a, int left, int right)
//{
//	//如果left>right,区间不存在
//	//left==right,只有一个元素,可以看成是有序的
//	if (left >= right)
//	{
//		return;
//	}
//	else
//	{
//		//单次排序
//		int keyi = partion3(a, left, right);
//		//分成左右区间,排序左右区间
//		QuickSort(a, left, keyi - 1);
//		QuickSort(a, keyi + 1, right);
//	}
//}
//链表快速排序函数
//void sortList(Node* phead) 
//{
//	// 处理空或单节点链表的情况
//	if (!phead || !phead->next)
//	{
//		return;
//	}
//
//	//求链表长度
//	int len = 0;
//	Node* cur = phead;
//	while (cur) 
//	{
//		len++;
//		cur = cur->next;
//	}
//
//	// 将链表转换为数组
//	PepInfo* arr = malloc(len * sizeof(PepInfo));
//	if (arr == NULL)
//	{
//		printf("malloc error\n");
//		exit(-1);
//	}
//	cur = phead;
//	for (int i = 0; i < len; i++) 
//	{
//		arr[i] = cur->data;
//		cur = cur->next;
//	}
//
//	// 快速排序
//	QuickSort(arr, 0, len - 1);
//
//	// 将数组转换为链表
//	cur = phead;
//	for (int i = 0; i < len; i++) 
//	{
//		cur->data = arr[i];
//		cur = cur->next;
//	}
//
//	free(arr);
//}//交换数据
void swap(Node* p1, Node* p2)
{struct PepInfo tmp = p1->data;p1->data = p2->data;p2->data = tmp;
}//进行排序
void sort(Node* phead)
{//空不排序assert(phead != NULL);Node* begin = phead;Node* end = phead->next;while (end){while (begin != end){if (begin->data.age > end->data.age){swap(begin,end);}begin = begin->next;}end = end->next;begin = phead;}
}//已有数据读取,可能要更改头指针,传二级指针修改一级指针
void StartFileEntry(Node** pphead)
{FILE* pf = fopen("data.txt", "rb");if (pf == NULL){printf("打开文件失败\n");exit(-1);}//保存读取到的数据PepInfo* tmp = (PepInfo*)malloc(sizeof(PepInfo));if (tmp == NULL){printf("malloc error\n");exit(-1);}//先读取一次fread(tmp, sizeof(PepInfo), 1, pf);//保存尾部节点地址Node* tail = NULL;//读取到结尾时feof()返回0,未读取到结尾返回非0值while (!feof(pf)){//生成新节点Node* NewNode = (Node*)malloc(sizeof(Node));if (NewNode == NULL){printf("malloc error\n");exit(-1);}NewNode->data = *tmp;NewNode->next = NULL;//第一次头为空,更新if (*pphead == NULL){*pphead = NewNode;tail = *pphead;}//头不为空,新节点链接到尾后面,尾指针更新else{tail->next = NewNode;tail = tail->next;}//继续读取fread(tmp, sizeof(PepInfo), 1, pf);}fclose(pf);
}//新数据录入
void UpdateFileEntry(Node* phead)
{FILE* pf = fopen("data.txt", "wb");if (phead == NULL){return;}else{while (phead){fwrite(&(phead->data), sizeof(PepInfo), 1, pf);phead = phead->next;}}fclose(pf);
}

test.c(主逻辑)

#define _CRT_SECURE_NO_WARNINGS 1
#include "contact.h"void menu()
{printf("********************************\n");printf("***** 1.增加      2.删除  ******\n");printf("***** 3.查找      4.修改  ******\n");printf("***** 5.排序      6.打印  ******\n");printf("***** 0.退出              ******\n");printf("********************************\n");}int main()
{Node* head = NULL;int input = 0;//原信息录入,这里用读模式打开,第一次需要手动创建文件StartFileEntry(&head);do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:AddContact(&head);printf("增加成功\n");break;case 2:DelContact(&head);break;case 3:{char name[max_name];printf("请输入要查找联系人的名字:");scanf("%s", name);Node* pos = FindContact(head, name);if (pos == NULL){printf("查找失败\n");}else{printf("找到了\n");printf("%-20s\t%-5d\t%-5s\t%-20s\t%-20s\n",pos->data.name, pos->data.age, pos->data.sex,pos->data.tele, pos->data.addr);}break;}case 4:ModifyContact(head);break;case 5:sort(head);printf("排序成功\n");break;case 6:PrintContact(head);break;case 0:printf("退出\n");break;default:printf("非法输入,重新输入\n");break;}} while (input);//新信息录入UpdateFileEntry(head);return 0;
}


http://www.ppmy.cn/news/83277.html

相关文章

netcore都有什么设计模式

.NET Core 框架支持许多设计模式&#xff0c;以下是一些常见的设计模式&#xff1a; 一、抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;&#xff1a;提供一种将一组相关或相互依赖的对象创建起来的方式&#xff0c;而无需指定其具体类。 抽象工厂模式是一种…

Java中常见的运行时异常

ArithmeticException :算数运算异常&#xff0c;由于除数为0引起的异常&#xff1b;ClassCast Exception: 类型转换异常&#xff0c;当把一个对象归为某个类&#xff0c;但实际上此对象并不是由这个类创建的&#xff0c;也不是其子类创建的&#xff0c;则会引起异常&#xff1b…

ES6中的解构赋值

解构赋值 在JavaScript中&#xff0c;解构赋值是一项非常有用的特性&#xff0c;它可以让我们方便地从数组和对象中提取值并赋给变量。这个特性实际上是一种语法糖&#xff0c;它可以让我们更加方便地获取想要的数据&#xff0c;减少代码的冗余和复杂度。 数组解构赋值 我们…

【Linux之IO系统编程学习】05.标准IO之 fopen_fgets_fputs

【Linux之IO系统编程学习】 项目代码获取&#xff1a;https://gitee.com/chenshao777/linux_-io.git &#xff08;麻烦点个免费的Star哦&#xff0c;您的Star就是我的写作动力&#xff01;&#xff09; 05.标准IO之 fopen_fgets_fputs 1、fopen函数 FILE *fopen(const char *…

Spring框架之体系结构和目录结构

Spring是由Rob Jonson租住和开发的一个分层的JavaEE/SE一站式&#xff08;full stack&#xff09;轻量级开发框架&#xff0c;他的核心思想是控制翻转(Inversion of Control IOC)和面向切面(Aspect Oriented Programming, aop)的编程&#xff0c;其中IoC是Spring的基础&#xf…

《NFT区块链进阶指南二》Etherscan验证Solidity智能合约(Remix插件验证)

文章目录 一、验证说明二、Etherscan Key三、验证插件四、源码认证4.1 Remix验证&#xff08;推荐&#xff09;4.1.1 无构造参数合约验证4.1.2 有构造参数合约验证 4.2 单文件验证&#xff08;不推荐&#xff09;4.3 Hardhat部署&#xff08;按照需要&#xff09; 五、验证结果…

Java的Proxy,一种思考和解决问题的方法

代理模式 静态代理功能列表&#xff08;接口&#xff09;原有功能&#xff0c;功能的实现在不破坏原功能的情况下EnhanceTest JDK 动态代理Cglib 代理模式Callback的MethodInterceptortest 静态代理 在不破坏原有功能的情况下&#xff0c;进行升级改造。 使用场景&#xff0c;…

Hyperledger Fabric理解

在Hyperledger Fabric中&#xff0c;Peer和Orderer是两个不同的角色&#xff0c;它们各自扮演不同的角色&#xff0c;但也需要相互协同合作来支持Fabric网络的顺畅运行。 Peer是Hyperledger Fabric网络中负责维护分类帐&#xff08;Ledger&#xff09;、安装链码&#xff08;C…