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

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

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

            ⏩ 文章专栏:《C语言项目实战》

                                  期待您的关注

1b7335aca73b41609b7f05d1d366f476.gif

目录

一、引言

二、单链表的基本概念

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

四、通讯录的数据结构

五、通讯录的接口

 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;
}

八、测试与验证

10ebab5873ab425ea4f5ed335c9c3e15.png

79b37c09f10b439f9142163bbcefadc6.png

296326d4c7ab42339ae3b8a10f97f60b.png

九、写在最后

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

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

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

  • 75
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 22
    评论
C语言中,实现快速排序(QuickSort)算法通常用于数组或动态数组(如数组或动态分配的内存块),因为它们支持随机访问。然而,对于单链表这种顺序存储结构,由于不支持直接索引,快速排序的传统方法并不适用,因为快速排序依赖于元素的原地交换。对于链表,更合适的是使用迭代或递归的方式来重新排列节点,但这已经不再是标准的快速排序,而是类似于“分治”的思想,但操作方式有所不同。 一种常见的链表排序算法是归并排序,因为它适合链表的结构,不需要频繁的元素交换。如果要用类似快速排序的思想对链表进行排序,通常会采用“分而治之”的策略,先分割链表,然后递归地处理子链表,最后合并结果。但这种实现会涉及到递归和链表的操作,比较复杂。 如果你想了解如何在链表实现类似分治的排序,可以考虑以下步骤: 1. **选择基准**:在链表中选择一个节点作为基准,通常是头节点或者随机节点。 2. **分割链表**:将链表分为两部分,一部分包含所有小于基准的节点,另一部分包含所有大于等于基准的节点。 3. **递归排序**:对这两部分链表分别进行递归排序。 4. **合并链表**:将两个已排序的链表合并成一个有序链表。 这里需要注意的是,没有原地交换操作,链表的合并可能会涉及到链表节点的插入和删除操作。 如果你对具体的代码实现感兴趣,我可以提供一个简化版的伪代码,但完整的C语言代码实现会涉及较多细节,包括链表节点的结构定义和递归调用。如果你想要详细的代码示例,请告诉我,我会尽力为你提供帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值