【C语言项目实战】使用单链表实现通讯录

news2024/11/17 10:08:32

                                                 

                                               💓 博客主页:倔强的石头的CSDN主页 

                                               📝Gitee主页:倔强的石头的gitee主页

                                                                期待您的关注

目录

一、引言

二、单链表的基本概念

三、通讯录项目的需求分析

四、通讯录的数据结构

五、通讯录的接口

 1.通讯录初始化 / 导入外部数据

2.添加联系人信息

3.删除联系人信息

4.查找联系人信息

5.修改联系人信息

6.展示联系人信息

7.导出数据到文件

8.通讯录销毁

六、主函数中通讯录操作

1.通讯录菜单

2.通讯录人机交互操作

七、各文件的实现代码

SLinkList.h

SLinkList.c

 Contact.h

Contact.c

test.c

八、测试与验证

九、写在最后


一、引言

在数字化时代,通讯录作为我们日常生活中不可或缺的一部分,扮演着记录和管理联系人信息的重要角色。随着智能手机的普及,人们对于通讯录的功能和性能要求也在不断提高。为了更好地满足这些需求,我们有必要对通讯录的实现方式进行深入研究和探索。

在众多的数据结构中,单链表以其独特的优势成为了实现通讯录的一种理想选择。单链表是一种线性数据结构,它通过每个节点中的指针链接在一起,形成一个有序的链表。相比于数组等其他数据结构,单链表在插入、删除操作上具有更高的效率,因为它不需要像数组那样移动大量的元素。此外,单链表在内存使用上也更加灵活,可以根据需要动态地分配和释放内存空间。

因此,本文旨在探讨如何使用单链表来实现一个高效、灵活的通讯录项目。我们将首先介绍单链表的基本概念和基本操作,然后分析通讯录项目的需求,并设计相应的数据结构和接口。接下来,我们将详细实现通讯录类的各个功能,并进行测试和验证。最后,我们将对项目进行总结和反思,并提出改进方向。

通过本文的介绍和实践,读者将能够深入理解单链表在通讯录项目中的应用,掌握使用单链表实现通讯录的基本方法和技巧。同时,本文也为读者提供了一个实际的项目案例,有助于提升读者的编程能力和解决问题的能力。

二、单链表的基本概念

通讯录项目的实现直接借用了单链表实现的头文件SLinkList.h 和 源文件SLinkList.c

   关于单链表的问题请参照前置文章

【数据结构/C语言】单链表的实现-CSDN博客

 对单链表有了深入的理解之后才能更好的实现通讯录项目

三、通讯录项目的需求分析

  1. 能够存储较多的联系人信息,并且能够高效地管理内存,避免不必要的内存浪费
  2. 能够保存用户信息:名字、性别、年龄、电话、地址等
  3. 增加联系⼈信息
  4. 删除指定联系⼈
  5. 查找指定联系⼈
  6. 修改指定联系⼈
  7. 显示联系⼈信息
  8. 实现数据的导入导出

四、通讯录的数据结构

以下是用结构体记录通讯录单个联系人信息的信息,对应单链表单个节点的数据部分的数据类型

#define NAME_MAX 100
#define SEX_MAX 4
#define TEL_MAX 11
#define ADDR_MAX 100

typedef struct PersonInfo
{
    char name[NAME_MAX];//姓名
    char sex[SEX_MAX];  //性别
    int age;            //年龄
    char tel[TEL_MAX];  //电话
    char addr[ADDR_MAX];//地址
}PeoInfo;

 同时,单链表头文件中对单链表结构的数据部分定义有所修改

typedef PeoInfo SLTDataType;//类型重定义
typedef struct SListNode
{
	SLTDataType data;//数据
	struct SListNode* next;//指针
}SLTNode;

 注意:

因为单链表的头文件需要用到通讯录的头文件的联系人结构体定义,所以在单链表头文件中包含了通讯录头文件。

但通讯录头文件中又需要用到单链表中对节点的定义,头文件不能互相包含,所以应当在通讯录头文件中包含一条对单链表的结构体的前置声明

typedef struct SListNode 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);

 1.通讯录初始化 / 导入外部数据

单链表实现的通讯录因为不带有额外的头节点,并且链表每个节点都是独立的,所以初始化不需要额外的操作,只需要从外部文件导入通讯录数据即可

这里将导入数据单独封装成一个函数,以便代码复用

由初始化函数来调用导入数据函数

导入外部数据的函数功能:

  •  以二进制读方式打开文件(注意:以读的方式打开文件,必须保证文件存在,否则会出错
  • 判断是否成功
  • 循环读取数据,每读取一条就尾插到单链表
void LoadContact(contact** con)//导入数据到通讯录
{
	FILE* pf = fopen("contact.txt", "rb");//以二进制读方式打开文件
	if (pf == NULL)//判空
	{
		perror("fopen");
		return;
	}
	PeoInfo po;
	while (fread(&po, sizeof(po), 1, pf))//循环读取数据
	{
		SLTPushBack(con, po);//尾插到单链表
	}
	printf("数据载入成功!\n");
	fclose(pf);
	pf = NULL;
}
/初始化通讯录
void InitContact(contact** con)
{
	LoadContact(con);//导入外部数据
}

2.添加联系人信息

添加联系人信息的函数功能:

  • 创建一个联系人的结构体变量
  • 逐个录入信息至该变量
  • 将该结构体变量和通讯录链表的首节点地址作为参数,一起传给底层的单链表尾插函数
//添加通讯录数据
void AddContact(contact** con)
{
	assert(con);//二级指针判空
	PeoInfo po;
	printf("请按提示输入要添加的联系人信息\n");
	printf("请输入姓名:\n");
	scanf("%s", po.name);
	printf("请输入性别:\n");
	scanf("%s", po.sex);
	printf("请输入年龄:\n");
	scanf("%d", &po.age);
	printf("请输入电话:\n");
	scanf("%s", po.tel);
	printf("请输入地址:\n");
	scanf("%s", po.addr);

	SLTPushBack(con, po);//调用单链表函数尾插
	printf("添加联系人成功\n");

}

3.删除联系人信息

删除联系人记录需要封装一个查找联系人函数单独实现查找,无其他功能)

单独的查找函数的函数功能:

  • 这里以姓名作为关键值查找,接收一个链表首地址和关键值信息
  • 遍历链表,如果找到该联系人,返回节点地址
  • 否则返回空指针
//封装的单独查找函数
contact* FindByname(contact*con,char name[])
{
	assert(con);//二级指针判空
	contact* pcur = con;//遍历链表的指针
	while (pcur)
	{
		if (strcmp(pcur->data.name, name) == 0)//字符串比对
			return pcur;
		pcur = pcur->next;
	}
	return NULL;
}

 删除联系人记录的函数功能:

  • 录入要删除的联系人姓名
  • 调用封装的查找函数
  • 如果找到,调用单链表实现的删除指定节点函数
  • 如果找不到,报错
//删除通讯录数据
void DelContact(contact** con)
{
	assert(con&&*con);//二级指针判空,链表判空
	printf("请输入要删除的联系人姓名:");
	char name[NAME_MAX];
	scanf("%s", name);
	contact* del = FindByname(*con, name);//调用单独的查找函数
	if (del == NULL)
	{
		printf("要删除的联系人不存在!\n");
		return;
	}
	SLTErase(con, del);//调用单链表删除指定元素
	printf("删除联系人成功!\n");

}

4.查找联系人信息

查找联系人信息的函数功能:

  • 这里不同于上面的功能单一的查找函数
  • 作用是根据录入的关键值查找并打印该联系人信息或报错
  • 这里依然以姓名作为关键值查找
//查找通讯录数据
void FindContact(contact* con)
{
	
	printf("请输入要查找的联系人姓名:");
	char name[NAME_MAX];
	scanf("%s", name);
	contact* pcur = FindByname(con, name);//调用已经实现的查找函数
	if (pcur == NULL)
	{
		printf("要查找的联系人不存在!\n");
		return;
	}
	printf("%-10s%-10s%-10s%-10s%-10s\n",
		"姓名", "性别", "年龄", "电话", "地址"
	);//打印该联系人信息
	printf("%-10s%-10s%-10d%-10s%-10s\n",
		pcur->data.name,
		pcur->data.sex,
		pcur->data.age,
		pcur->data.tel,
		pcur->data.addr
	);
}

5.修改联系人信息

修改联系人信息的函数功能:

  • 依然以姓名作为关键值(因为可以重复利用封装的查找函数)
  • 录入姓名,调用单独的查找函数
  • 若找到指定联系人依次修改该联系人的各部分信息,赋值给该节点
  • 否则,该联系人不存在,报错
//修改通讯录数据
void ModifyContact(contact** con)
{
	assert(con);
	printf("请输入要修改的联系人姓名:");
	char name[NAME_MAX];
	scanf("%s", name);
	contact* pcur = FindByname(*con, name);//调用已经实现的查找函数
	if (pcur == NULL)
	{
		printf("要修改的联系人不存在!\n");
		return;
	}
	printf("请输入修改后的联系人姓名: ");
	scanf("%s", pcur->data.name);
	printf("请输入修改后的联系人性别: ");
	scanf("%s", pcur->data.sex);
	printf("请输入修改后的联系人年龄: ");
	scanf("%d", &pcur->data.age);
	printf("请输入修改后的联系人电话: ");
	scanf("%s", pcur->data.tel);
	printf("请输入修改后的联系人地址: ");
	scanf("%s", pcur->data.addr);

	printf("修改联系人信息成功!\n");
}

6.展示联系人信息

展示联系人信息的函数功能:

  • 先打印表头信息
  • 创建一个遍历链表的指针
  • 逐个访问链表的每一个节点,每行打印该节点的数据部分
//展示通讯录数据
void ShowContact(contact* con)
{
	if (con == NULL)//对空链表的特殊处理
	{
		printf("NULL\n");
		return;
	}
	printf("%-10s%-10s%-10s%-10s%-10s\n",
		"姓名", "性别", "年龄", "电话", "地址"
	);//表头

	contact* pcur = con;
	while (pcur)//遍历链表,打印每个节点的联系人信息
	{
		printf("%-10s%-10s%-10d%-10s%-10s\n",
			pcur->data.name,
			pcur->data.sex,
			pcur->data.age,
			pcur->data.tel,
			pcur->data.addr
		);
		pcur = pcur->next;
	}
}

7.导出数据到文件

数据导出的函数功能:

  • 以二进制写方式打开文件
  • 循环遍历链表,将每个节点的数据(每个联系人信息)输出到外部文件
//导出数据
void SaveData(contact* con)
{
	FILE* pf = fopen("contact.txt", "wb");//以二进制写方式打开文件
	if (pf == NULL)
	{
		perror("fopen\n");
		exit(1);
	}
	contact* pcur = con;
	while (pcur)//遍历链表,将通讯录数据输出到文件中
	{
		fwrite(pcur, sizeof(contact), 1, pf);
		pcur = pcur->next;
	}
	fclose(pf);
	free(pf);
	pf = NULL;
}

8.通讯录销毁

通讯录销毁的函数功能:

  • 销毁之前需要先调用导出数据的函数,将联系人信息保存下来
  • 调用单链表中已实现的链表销毁函数——循环遍历链表,释放每一个动态申请空间的节点
//销毁通讯录数据
void DestroyContact(contact** con)
{
	SaveData(*con);//调用函数导出数据到文件
	SListDesTroy(con);//调用单链表函数销毁通讯录
}

六、主函数中通讯录操作

1.通讯录菜单

菜单函数的功能:

  • 封装一个函数向用户展示通讯录项目的功能以及每项功能对应的选项
void menu()
{
	printf("\n######################################\n");
	printf("###########--——通讯录菜单——--#########\n");
	printf("#####1.添加联系人   2.删除联系人######\n");
	printf("#####3.修改联系人   4.查找联系人######\n");
	printf("#########  5.展示全部联系人 ##########\n");
	printf("#########  0.退出通讯录程序 ##########\n");
	printf("######################################\n\n");
}

2.通讯录人机交互操作

通讯录人机交互部分功能:

  • 首先主函数运行,初始化通讯录,从外部载入数据
  • 然后通过一个 do while循环(保证程序至少运行一次)和 swtich语句(用户选择指定选项对应指定的功能)配合完成对通讯录的操作
  • 最后,用户选择结束操作后,将数据保存至外部文件,销毁通讯录
contact* con = NULL;
InitContact(&con);//初始化
int op = 0;//选项
do {
	menu();
	printf("请选择您的操作:  ");
	scanf("%d", &op);
	switch (op)
	{
	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 (op);
DestroyContact(&con);//销毁

七、各文件的实现代码

单链表中已有文件——

SLinkList.h

单链表结构定义及函数声明头文件

//SLinkList.h
#define _CRT_SECURE_NO_WARNINGS 1
//单链表结构定义及函数声明头文件
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include"Contact.h"
#include<string.h>
typedef PeoInfo SLTDataType;//类型重定义
typedef struct SListNode
{
	SLTDataType data;//数据
	struct SListNode* next;//指针
}SLTNode;


void SLTPrint(SLTNode* phead);//链表打印

SLTNode* NewNode(SLTDataType x);//申请节点

//头部插入删除/尾部插入删除
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);

SLinkList.c

单链表方法实现源文件

//SLinkList.c
//单链表方法实现源文件
#include"SLinkList.h"
//void SLTPrint(SLTNode* phead)//链表打印
//{
//	SLTNode* pcur = phead;
//	while (pcur)//pcur!=NULL
//	{
//		printf("%d->", pcur->data);//打印该节点数据
//		pcur = pcur->next;//指针指向下一个节点
//	}
//	printf("NULL\n");
//}

SLTNode* NewNode(SLTDataType x)//申请节点
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)//注意这里不要写成一个=
	{
		perror("newnode");
		exit(1);//如果申请节点失败,异常退出程序
	}
	newnode->data = x; //数据初始化
	newnode->next = NULL;//指针初始化
	return newnode;//返回新申请节点的地址
}

//头部插入删除/尾部插入删除
void SLTPushBack(SLTNode** pphead, SLTDataType x)//尾插
{
	assert(pphead);//二级指针不能为空,否则解引用就会报错
	SLTNode* newnode = NewNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;//如果链表为空,新节点即为第一个节点
	}
	else
	{
		SLTNode* pcur = *pphead;
		while (pcur->next!=NULL)//找到链表的尾节点
		{
			pcur = pcur->next;
		}
		pcur->next = newnode;//如果不对空链表分别处理
	}						//此处就会对空指针解引用
}
void SLTPushFront(SLTNode** pphead, SLTDataType x)//头插
{
	assert(pphead);//二级指针不能为空,否则解引用就会报错
	SLTNode* newnode = NewNode(x);
	newnode->next = *pphead;//新节点next指针指向原来的首节点
	*pphead = newnode;//新节点成为首节点
}

void SLTPopBack(SLTNode** pphead)//尾删
{
	assert(pphead && *pphead);//二级指针不能为空,链表不能为空
	if ((*pphead)->next == NULL)//处理链表只有一个节点的情况
	{
		free(*pphead);
		*pphead = NULL;
	}
	else//处理正常情况
	{
		SLTNode* pcur = *pphead;//找到指针的最后一个节点
		SLTNode* prev = *pphead;//找到指针的倒数第二个节点
		while (pcur->next != NULL)
		{
			prev = pcur;
			pcur = pcur->next;
		}
		free(pcur);
		pcur = NULL;
		prev->next = NULL;//如果不做特殊处理,此处就会对空指针解引用
	}
}

void SLTPopFront(SLTNode** pphead)//头删
{
	assert(pphead && *pphead);//二级指针不能为空,链表不能为空
	SLTNode* next = (*pphead)->next;//存储第二个节点
	free(*pphead);//删除第一个节点
	*pphead = next;//链表指向第二个节点
}

//查找
//SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
//{
//	SLTNode* pcur = phead;
//	while (pcur)
//	{
//		if (pcur->data == x)
//			return pcur;//如果找到,返回节点地址
//	}
//	return NULL;//对未找到的情况和链表为空的情况都可以处理
//}

//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && *pphead);//二级指针不能为空,链表不能为空
	assert(pos);//指定位置必须存在
	SLTNode* newnode = NewNode(x);
	if (*pphead == pos)//如果要插入到第一个节点之前
	{
		SLTPushFront(pphead, x);//可以直接调用头插
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)//需要先找到pos的前一个节点
		{						//如果不分别处理,最后就会对空指针解引用
			prev = prev->next;
		}
		newnode->next = pos;//新节点指向pos
		prev->next = newnode;//原pos的前一个节点指向新节点
	}

}

//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);//pos节点必须存在
	SLTNode* newnode = NewNode(x);
	newnode->next = pos->next;//新节点的next指针指向原pos的下一个节点
	pos->next = newnode;//pos的next指针指向新节点
}

//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);//二级指针不能为空,链表不能为空
	assert(pos);//指定位置必须存在
	if (pos == *pphead)//如果删除的是首节点,或链表只有一个节点
	{
		SLTPopFront(pphead);
	}
	else//链表有多个节点,且删除的不是首节点
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)//如果不分开处理,这里就可能对空指针解引用
		{
			prev = prev->next;
		}
		prev->next = pos->next;//找到前一个指针,并将其next指针指向pos的next指针所指节点
		free(pos);
		pos = NULL;
	}
}

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);//pos节点必须存在且pos之后必须存在节点
	SLTNode* del = pos->next;//预先存储要删除的节点,防止修改指针之后找不到要删除的节点
	pos->next = del->next;
	free(del);
	del = NULL;
}

//销毁链表
void SListDesTroy(SLTNode** pphead)
{
	assert(pphead);//二级指针不能为空,链表不能为空
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;//需要预先存储当前要删除节点的下一个节点
		free(pcur);					//否则就找不到下一个节点了
		pcur = next;
	}
	*pphead = NULL;//删除完所有节点之后,链表置空
}

 Contact.h

通讯录结构定义及函数声明头文件

//Contact.h
#pragma once
#define NAME_MAX 100
#define SEX_MAX 4
#define TEL_MAX 11
#define ADDR_MAX 100

//前置声明
typedef struct SListNode contact;//声明并重命名

//用户数据
typedef struct PersonInfo
{
    char name[NAME_MAX];
    char sex[SEX_MAX];
    int age;
    char tel[TEL_MAX];
    char addr[ADDR_MAX];
}PeoInfo;


//初始化通讯录
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);

Contact.c

通讯录方法实现源文件

//Contact.c
#include"Contact.h"
#include"SLinkList.h"
//导入数据到通讯录
void LoadContact(contact** con)
{
	FILE* pf = fopen("contact.txt", "rb");//以二进制读方式打开文件
	if (pf == NULL)//判空
	{
		perror("fopen");
		return;
	}
	PeoInfo po;
	while (fread(&po, sizeof(po), 1, pf))//循环读取数据
	{
		SLTPushBack(con, po);//尾插到单链表
	}
	printf("数据载入成功!\n");
	fclose(pf);
	pf = NULL;
}
//初始化通讯录
void InitContact(contact** con)
{
	LoadContact(con);//导入外部数据
}
//添加通讯录数据
void AddContact(contact** con)
{
	assert(con);//二级指针判空
	PeoInfo po;
	printf("请按提示输入要添加的联系人信息\n");
	printf("请输入姓名:\n");
	scanf("%s", po.name);
	printf("请输入性别:\n");
	scanf("%s", po.sex);
	printf("请输入年龄:\n");
	scanf("%d", &po.age);
	printf("请输入电话:\n");
	scanf("%s", po.tel);
	printf("请输入地址:\n");
	scanf("%s", po.addr);

	SLTPushBack(con, po);//调用单链表函数尾插
	printf("添加联系人成功\n");

}

//封装的单独查找函数
contact* FindByname(contact*con,char name[])
{
	assert(con);//二级指针判空
	contact* pcur = con;//遍历链表的指针
	while (pcur)
	{
		if (strcmp(pcur->data.name, name) == 0)//字符串比对
			return pcur;
		pcur = pcur->next;
	}
	return NULL;
}

//删除通讯录数据
void DelContact(contact** con)
{
	assert(con&&*con);//二级指针判空,链表判空
	printf("请输入要删除的联系人姓名:");
	char name[NAME_MAX];
	scanf("%s", name);
	contact* del = FindByname(*con, name);//调用单独的查找函数
	if (del == NULL)
	{
		printf("要删除的联系人不存在!\n");
		return;
	}
	SLTErase(con, del);//调用单链表删除指定元素
	printf("删除联系人成功!\n");

}

//展示通讯录数据
void ShowContact(contact* con)
{
	if (con == NULL)//对空链表的特殊处理
	{
		printf("NULL\n");
		return;
	}
	printf("%-10s%-10s%-10s%-10s%-10s\n",
		"姓名", "性别", "年龄", "电话", "地址"
	);//表头

	contact* pcur = con;
	while (pcur)//遍历链表,打印每个节点的联系人信息
	{
		printf("%-10s%-10s%-10d%-10s%-10s\n",
			pcur->data.name,
			pcur->data.sex,
			pcur->data.age,
			pcur->data.tel,
			pcur->data.addr
		);
		pcur = pcur->next;
	}
}

//查找通讯录数据
void FindContact(contact* con)
{
	
	printf("请输入要查找的联系人姓名:");
	char name[NAME_MAX];
	scanf("%s", name);
	contact* pcur = FindByname(con, name);//调用已经实现的查找函数
	if (pcur == NULL)
	{
		printf("要查找的联系人不存在!\n");
		return;
	}
	printf("%-10s%-10s%-10s%-10s%-10s\n",
		"姓名", "性别", "年龄", "电话", "地址"
	);//打印该联系人信息
	printf("%-10s%-10s%-10d%-10s%-10s\n",
		pcur->data.name,
		pcur->data.sex,
		pcur->data.age,
		pcur->data.tel,
		pcur->data.addr
	);
}

//修改通讯录数据
void ModifyContact(contact** con)
{
	assert(con);
	printf("请输入要修改的联系人姓名:");
	char name[NAME_MAX];
	scanf("%s", name);
	contact* pcur = FindByname(*con, name);//调用已经实现的查找函数
	if (pcur == NULL)
	{
		printf("要修改的联系人不存在!\n");
		return;
	}
	printf("请输入修改后的联系人姓名: ");
	scanf("%s", pcur->data.name);
	printf("请输入修改后的联系人性别: ");
	scanf("%s", pcur->data.sex);
	printf("请输入修改后的联系人年龄: ");
	scanf("%d", &pcur->data.age);
	printf("请输入修改后的联系人电话: ");
	scanf("%s", pcur->data.tel);
	printf("请输入修改后的联系人地址: ");
	scanf("%s", pcur->data.addr);

	printf("修改联系人信息成功!\n");
}

//导出数据
void SaveData(contact* con)
{
	FILE* pf = fopen("contact.txt", "wb");//以二进制写方式打开文件
	if (pf == NULL)
	{
		perror("fopen\n");
		exit(1);
	}
	contact* pcur = con;
	while (pcur)//遍历链表,将通讯录数据输出到文件中
	{
		fwrite(pcur, sizeof(contact), 1, pf);
		pcur = pcur->next;
	}
	fclose(pf);
	free(pf);
	pf = NULL;
}

//销毁通讯录数据
void DestroyContact(contact** con)
{
	SaveData(*con);//调用函数导出数据到文件
	SListDesTroy(con);//调用单链表函数销毁通讯录
}

test.c

主函数测试文件

#include"Contact.h"
#include"SLinkList.h"
void menu()
{
	printf("\n######################################\n");
	printf("###########--——通讯录菜单——--#########\n");
	printf("#####1.添加联系人   2.删除联系人######\n");
	printf("#####3.修改联系人   4.查找联系人######\n");
	printf("#########  5.展示全部联系人 ##########\n");
	printf("#########  0.退出通讯录程序 ##########\n");
	printf("######################################\n\n");
}
int main()
{
	//test1();
	contact* con = NULL;
	InitContact(&con);//初始化
	int op = 0;//选项
	do {
		menu();
		printf("请选择您的操作:  ");
		scanf("%d", &op);
		switch (op)
		{
		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 (op);
	DestroyContact(&con);//销毁
	return 0;
}

八、测试与验证

九、写在最后

本文所有代码已经过多轮测试,可以直接复制使用。

如果您发现某处代码存在问题以及对程序的改进意见,欢迎私信或评论指点。

 💓 博客主页:倔强的石头的CSDN主页

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1700202.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python 机器学习 基础 之 数据表示与特征工程 【分类变量】的简单说明

Python 机器学习 基础 之 数据表示与特征工程 【分类变量】的简单说明 目录 Python 机器学习 基础 之 数据表示与特征工程 【分类变量】的简单说明 一、简单介绍 二、数据表示与特征工程 数据表示 特征工程 三、分类变量 1、One-Hot编码&#xff08;虚拟变量&#xff09…

rk3568_spinlock

文章目录 前言1、spinlock是什么?2、自旋锁实验2.1源码2.2 结果图总结前言 本文记录在rk3568开发板做的自旋锁实验。通过自旋锁控制state变量来限制只有一个应用程序来打开驱动设备。 1、spinlock是什么? spinlock称为自旋锁,如果获取不到资源,就只能一直傻傻地等待资源被…

提高Java编程效率:ArrayList类的使用技巧

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

乐理学习-音及音名

1. 我觉得练习题很重要。我要得到一个反馈 所以我想没学习完书中的一节就要把练习题做下来&#xff0c;虽然慢点也可以。 2. 做个小计划。 今天计算了一下学完《基本乐理-李重光》如果每天3张。也要80天干完&#xff0c;希望能有一天可以学习7张的速度的时候。 3. 练习记录…

如何利用已有数据对模型进行微调

1.langchain整合llm做知识问答 利用LangChain的能力来结合检索和生成&#xff0c;形成一个知识增强的问答系统&#xff08;不涉及对模型的微调&#xff09;&#xff0c;而是利用llm从文档检索到问题解答。 langchain整合llm做知识检索 2.微调llm模型 1、首先是我们的数据集&…

从0开始学统计-秩和检验

1.什么是秩和检验&#xff1f; 秩和检验&#xff0c;也称为Wilcoxon 秩和检验&#xff0c;是一种非参数统计检验方法&#xff0c;用于比较两个独立样本的中位数是否有显著差异。它不要求数据满足正态分布假设&#xff0c;因此适用于小样本或者数据不满足正态分布假设的情况。 …

Android 快速调试网络 复制curl 到postMan

搜索这个插件 官网地址&#xff1a;https://github.com/itkacher/OkHttpProfiler 集成教程也在里面集成完毕后右下角有一个入口点进去可以复制curl| 插件名称&#xff1a;Okhttp Profiler 真的很好用&#xff01;

Ubuntu server 24 源码安装Quagga 支持动态路由协议ospf bgp

1 下载:GitHub - Quagga/quagga: Quagga Tracking repository - Master is at http://git.savannah.gnu.org/cgit/quagga.git 2 安装 #安装依赖包 sudo apt install gcc make libreadline-dev pkg-config #解压 tar zxvf quagga-1.2.4.tar.gz cd quagga-1.2.4/sudo ./co…

Linux(五)

Linux&#xff08;五&#xff09; 结构体如何定义一个结构体如何定义一个结构体变量结构体变量如何访问成员如何定义一个指向结构体的指针 动态分配空间 malloc,free在堆区分配5个存Stu的空间 #include <stdio.h> //编写一子函数,对pstr中的值进行输出 /*void output(int…

【开源】租房管理系统 JAVA+Vue+SpringBoot+MySQL

目录 一、系统介绍 租客屋主模块 房源信息模块 租客评价模块 房源订单模块 留言板模块 二、系统截图 三、核心代码 一、系统介绍 基于Vue.js和SpringBoot的租房管理系统&#xff0c;分为管理后台和用户网页端&#xff0c;可以给管理员、租客和屋主角色使用&#xff0c…

Layui设置table表格中时间的显示格式

1、问题概述? 【数据库中的时间格式】 【Layui中table表格默认的显示格式】 默认的格式中会显示时间的毫秒单位,但是这个毫秒有时候是不需要的。 总结:这个时候我们就需要定义table表格中的时间显示格式。 2、解决办法? 【解决后时间的显示格式】 【解决办法1:通过字符…

AI图书推荐:ChatGPT解码—人工智能增强生活指南

《ChatGPT解码—人工智能增强生活指南》&#xff08;ChatGPT Decoded. A Beginners Guide to AI-Enhanced Living &#xff09;是一本由 大卫维恩斯&#xff08;David Wiens &#xff09;所著的书籍&#xff0c;旨在帮助读者了解并有效利用GPT-4语言模型这一强大工具来提升日常…

开发依赖与运行依赖

1. 概念 开发依赖&#xff1a;devDependencies 运行依赖&#xff1a;dependencies 2. 理解 &#xff08;1&#xff09;devDependencies 在线上状态不需要使用的依赖&#xff0c;就是开发依赖。为什么 npm 要把它单独分拆出来呢&#xff1f;最终目的是为了减少 node_modul…

ESP32-C6接入巴法云,Arduino方式

ESP32-C6接入巴法云&#xff0c;Arduino方式 第一、ESP32-C6开发环境搭建第一步&#xff1a;安装arduino IDE 软件第二步&#xff1a;安装esp32库第三&#xff1a;arduino 软件设置 第二&#xff1a;简单AP配网程序第一步&#xff1a;程序下载第二步&#xff1a;程序使用第三步…

《德米安:彷徨少年时》

文前 我之所愿无非是尝试依本性而生活&#xff0c; 却缘何如此之难&#xff1f; 强盗 疏于独立思考和自我评判的人只能顺应现成的世俗法则&#xff0c;让生活变轻松。其他人则有自己的戒条&#xff1a;正派人惯常做的事于他可能是禁忌&#xff0c;而他自认合理的或许遭他人唾…

Mac安装pytorch

先下载 Anaconda | The Operating System for AI 网速慢&#xff0c;用中国大陆镜像&#xff1a;NJU Mirror 之前装python3时用的是pip3&#xff0c;这里说一下这pip与conda的区别 Conda和pip都是Python包管理工具&#xff0c;用于安装和管理Python包 包管理范围&#xff1a…

简单的基于信号处理的心电信号ECG特征波分割方法(MATLAB)

正常的心电图中&#xff0c;每个心跳周期内包含三个主要的特征波&#xff1a;&#xff30;波、QRS波和&#xff34;波&#xff0c;如下图所示。心电特征波能够反映心脏的生理状态信息&#xff0c;通过对其形状、幅值和持续时间的分析&#xff0c;可以用来辅助诊断心血管疾病。对…

Java顺序表

Java顺序表 前言一、线性表介绍常见线性表总结图解 二、顺序表概念顺序表的分类顺序表的实现throw具体代码 三、顺序表会出现的问题 前言 推荐一个网站给想要了解或者学习人工智能知识的读者&#xff0c;这个网站里内容讲解通俗易懂且风趣幽默&#xff0c;对我帮助很大。我想与…

深入解析BGP与OSPF:互联网与企业网络中的路由协议对比

BGP&#xff08;Border Gateway Protocol&#xff09;和OSPF&#xff08;Open Shortest Path First&#xff09;是两种不同的路由协议&#xff0c;它们在互联网和企业网络中扮演着重要的角色。下面是它们各自特点的详细内容丰富&#xff1a; BGP&#xff08;Border Gateway Pro…

leecode 226 翻转二叉树、101 对称二叉树、104 二叉树的最大深度

leecode 226 翻转二叉树、101 对称二叉树、104 二叉树的最大深度 leecode 226 翻转二叉树 题目链接 &#xff1a;https://leetcode.cn/problems/invert-binary-tree/description/ 题目 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。…