C语言 零基础帮你快速了解【单链表结构】—— 通讯录项目

基于单链表结构实现通讯录项目

引言

在今天的数字化时代,通讯录管理成为我们日常生活中不可或缺的一部分。无论是在个人生活中还是在商业环境中,有效地管理联系人信息对于保持良好的沟通和组织联系至关重要。而在编程领域,通过实现一个通讯录项目,我们不仅可以提高对C语言的理解,还能够加深对数据结构的掌握。

本篇博客将介绍如何使用C语言和单链表数据结构来实现一个简单而功能强大的通讯录管理系统。通过这个项目,我们将学习如何在C语言中利用数据结构来组织和管理大量的联系人信息,以及如何实现基本的增加、删除、查找和修改功能。

在这个项目中,我们将使用单链表来存储联系人的信息。单链表是一种简单而灵活的数据结构,适用于需要频繁插入和删除操作的场景,正是这种特性使其成为实现通讯录的理想选择。通过使用单链表,我们可以轻松地添加、删除和更新联系人信息,同时保持数据的有序性和高效性。

在接下来的部分中,我们将详细介绍通讯录项目的设计和实现过程。我们将从创建和初始化单链表开始,逐步扩展功能,包括添加联系人、删除联系人、查找联系人以及显示整个通讯录等。通过这个项目,我们不仅可以提高对C语言和单链表的理解,还能够培养解决问题和组织数据的能力。

无论您是初学者还是有经验的程序员,这个项目都将为您提供一个宝贵的学习机会。通过动手实践,我们可以加深对编程语言和数据结构的理解,从而成为更加熟练和自信的开发者。让我们一起开始这个令人兴奋的学习之旅吧!

要想完成通讯录项目,我们需要掌握: 结构体文件操作、动态内存管理、链表这几个知识点。

1. 单链表的接口实现

1.1 通过结构体定义节点

typedef int SLTDataType;	//通过类型重命名,可以快速修改变量的类型
//当运用到通讯录的时候,要将int换为struct PersonInfo
typedef struct SListNode {
	SLTDataType data;		//data变量,来存储节点数据
	struct SListNode* next; //指针变量,用来保存下一个节点的地址
}SLTNode;					//将节点重命名为SLTNode

1.2 单链表的打印

void SLTPrint(SLTNode* phead)	//将单链表的头节点指针传入
{
	SLTNode* pcur = phead;		//定义一个pcur指针来接收头节点的地址
	while (pcur)	//pcur != NULL    //如果链表不为空则进入循环遍历打印节点
	{
		printf("%d->", pcur->data);	//打印节点的数据
		pcur = pcur->next;			//指向下一个节点
	}
	printf("NULL\n");				//链表全部打印完,最后一个节点的next指针指向NULL。
}

1.3 单链表节点的创建

SLTNode* SLTBuyNode(SLTDataType x)	//	将节点的数据传入,返回一个节点的指针
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));	//通过malloc函数向内存中申请一块连续可用的空间,并将地址返回newnode节点
	if (newnode == NULL)			//检查节点空间是否申请成功
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;				//存入节点数据
	newnode->next = NULL;			//并将节点的next指针指向NULL

	return newnode;					//返回节点指针
}

1.4 单链表的尾插法

void SLTPushBack(SLTNode** pphead, SLTDataType x)	//传入指向头节点指针的指针和节点数据
{
	assert(pphead);						//传入的指针不能为空
	SLTNode* newnode = SLTBuyNode(x);	//创建一个节点,并定义一个指针newnode指向该节点
										//*pphead 就是指向第一个节点的指针
	if (*pphead == NULL)				//如果头节点指针指向NULL,证明该链表为空
	{
		*pphead = newnode;				//将新创建的节点作为该链表的头节点
	}
	else								//如果链表不为空,就要找到该链表的尾节点进行插入操作
	{
		SLTNode* ptail = *pphead;		//创建一个指针ptail来接收头结点的地址
		while (ptail->next)				//只要指针指向的节点的下一个节点不为空就进入循环
		{
			ptail = ptail->next;		//通过遍历找到尾节点
		}
										//循环结束后,ptail指向的就是尾结点
		ptail->next = newnode;			//让尾节点的next指针指向要插入的节点
	}
}

1.5 单链表的头插法

void SLTPushFront(SLTNode** pphead, SLTDataType x)//传入指向头节点指针的指针和节点数据
{
	assert(pphead);								//传入的指针不能为空
	SLTNode* newnode = SLTBuyNode(x);			//创建一个节点,并定义一个指针newnode指向该节点
	newnode->next = *pphead;					//新创建的节点的next指针指向头结点
	*pphead = newnode;							//然后将新节点作为头结点
}

1.6 单链表的尾删法

void SLTPopBack(SLTNode** pphead)		//传入指向头节点指针的指针
{
	assert(pphead && *pphead); 			//链表不能为空
	//如果链表只有一个节点
	if ((*pphead)->next == NULL) //	-> 优先级高于*,所以*pphead要用()
	{
		free(*pphead);					//释放掉指针并置空
		*pphead = NULL;
	}
	else {		//如果链表有多个节点

		SLTNode* prev = *pphead;			//定义两个指针指向头节点
		SLTNode* ptail = *pphead;
		while (ptail->next)					//只要ptail的next不为空就进入循环
		{
			prev = ptail;					//通过两个指针找尾节点,当ptail的next为空时,循环停止
			ptail = ptail->next;			//此时,ptail指向要删除的尾节点,prev指向倒数第二个节点
		}
		free(ptail);						//将ptail指针释放掉并置为空
		ptail = NULL;
		prev->next = NULL;					//通过将prev的next指针指向的节点置为空,来达到删除节点的目的
	}
}

1.7 单链表的头删法

void SLTPopFront(SLTNode** pphead)		//传入指向头节点指针的指针
{
	assert(pphead && *pphead);			//链表不能为空
	//	-> 优先级高于*
	SLTNode* next = (*pphead)->next;	//定义一个next指针并指向要删除的头节点的下一个节点
	free(*pphead);						//释放点头节点达到删除节点的目的
	*pphead = next;						//将刚才的第二个节点设为头节点
}

1.8 单链表的在指定位置之前插入

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{			//传入指向头节点指针的指针,指定位置的指针,要插入的数据
	assert(pphead && *pphead);			//链表不能为空
	assert(pos);						//指定的位置不能为空
	SLTNode* newnode = SLTBuyNode(x);	//创造一个新节点并插入数据
	if (pos == *pphead)					//若pos == *pphead,说明是头插
	{
		SLTPushFront(pphead, x);		//直接调用头插函数即可
	}
	else 
	{
		SLTNode* prev = *pphead;		//定义一个指针prev指向头结点
		while (prev->next != pos)		//通过循环遍历找到指定的位置
		{
			prev = prev->next;			//循环结束时,prev指向的节点就是要进行尾插的节点
		}
		newnode->next = pos;			//尾插
		prev->next = newnode;
	}
}

在这里插入图片描述

1.9 单链表的在指定位置之后插入

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{			//传入指定位置的指针,要插入的数据
	assert(pos);						//指定的位置不能为空
	SLTNode* newnode = SLTBuyNode(x);	//创造一个新节点并插入数据
	newnode->next = pos->next;			//在指定位置直接进行尾插
	pos->next = newnode;
}

在这里插入图片描述

1.10 单链表的删除指定位置的节点

void SLTErase(SLTNode** pphead, SLTNode* pos)
{			//传入指向头节点指针的指针,指定位置的指针
	assert(pphead && *pphead);		//链表不能为空
	assert(pos);					//指定的位置不能为空
	//判断pos是头结点/pos不是头结点
	if (pos == *pphead)
	{
		//头删
		SLTPopFront(pphead);
	}
	else 
	{
		SLTNode* prev = *pphead;	//定义一个指针prev指向头结点
		while (prev->next != pos)	//通过循环遍历找到指定的位置
		{
			prev = prev->next;		//循环结束时,prev指向的节点就是要进行尾删前面的节点
		}
		prev->next = pos->next;		//使要删除的节点的前面节点指向要尾删的后面节点
		free(pos);					//删除节点
		pos = NULL;
	}
}

在这里插入图片描述

1.11 单链表的删除指定位置之后的节点

void SLTEraseAfter(SLTNode* pos)	//传入指定位置的指针
{
	assert(pos && pos->next);		//指定位置和其下一个节点都不能为空
	SLTNode* del = pos->next;		//定义一个del指针指向指定位置之后的节点
	pos->next = del->next;			//使要删除的节点的前面节点指向要尾删的后面节点
	free(del);						//删除节点
	del = NULL;
}

在这里插入图片描述

1.12 单链表的查找

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{			//传入指向链表头节点指针,和要查找的节点
	SLTNode* pcur = phead;		//定义一个指针prev指向头结点
	while (pcur)//等价于pcur != NULL
	{
		if (pcur->data == x)	//通过循环遍历寻找x节点
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//如果pcur == NULL了,依然没有返回,说明链表中没有该节点			
	return NULL;		
}

1.13 单链表的销毁

void SListDesTroy(SLTNode** pphead)	//传入指向头节点指针的指针
{
	assert(pphead && *pphead);		//头节点和指向头节点的指针都不能为空
	SLTNode* pcur = *pphead;		//定义一个指针pcur指向头结点
	while (pcur)					//通过循环遍历链表的每个节点并释放
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;					//最后将头结点也置为空
}

2. 通讯录项目的分析和实现

2.1 功能分析

  1. 至少能够存储100个人的通讯信息
  2. 能够保存用户信息:名字、性别、年龄、电话、地址等
  3. 增加联系人信息
  4. 删除指定联系人
  5. 查找指定联系人
  6. 修改指定联系人
  7. 显示联系人信息

2.2 代码实现

2.2.1 把本地的通讯录数据导入到链表结构
void LoadContact(contact** con)	//传入指向通讯录的头指针的指针
{	
	FILE* pf = fopen("contact.txt", "rb");	
	//打开一个名为 "contact.txt" 的文件,以二进制只读模式打开,并将文件指针赋值给pf。
	if (pf == NULL) 
	{			//文件指针不能为空
		perror("fopen error!\n");
		return;
	}
	//循环读取文件数据
	PeoInfo info;	//声明一个名为 info 的结构体变量,用于存储从文件中读取的单个联系人信息。
	while (fread(&info, sizeof(info), 1, pf))
	{
		SLTPushBack(con, info);
	}
	printf("历史数据导⼊通讯录成功!\n");
}
//在循环中,使用 fread 函数从文件中读取 sizeof(info) 大小的数据到 info 变量中,如果读取成功,则调用 SLTPushBack 函数将 info 中的信息添加到通讯录链表的末尾。
2.2.2 联系人信息
#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;
2.2.3 增加联系人
void AddContact(contact** con) 
{
	//传入指向通讯录的头指针的指针
	PeoInfo info;
	//声明一个名为 info 的结构体变量,用于存储联系人信息。
	printf("请输⼊姓名:\n");
	scanf("%s", &info.name);
	printf("请输⼊性别:\n");
	scanf("%s", &info.sex);
	printf("请输⼊年龄:\n");
	scanf("%d", &info.age);
	printf("请输⼊联系电话:\n");
	scanf("%s", &info.tel);
	printf("请输⼊地址:\n");
	scanf("%s", &info.addr);
	SLTPushBack(con, info);	//调用链表的尾插函数,储存联系人信息
	printf("插⼊成功!\n");
}
2.2.4 删除联系人
contact* FindByName(contact* con, char name[]) 
{
	//返回一个指向通讯录里联系人结构体的指针。该函数接受两个参数:一个指向通讯录头部的指针 con 和一个代表要查找的姓名的字符数组 name。
	contact* cur = con;	//定义一个指针cur指向联系人结构体指针
	while (cur)			//通过循环遍历整个通讯录链表
	{					//使用strcmp函数进行匹配
		if (strcmp(cur->data.name, name) == 0) {
			return cur;	//匹配成功返回指向当前联系人的指针
		}
		cur = cur->next;
	}
	return NULL;
}
void DelContact(contact** con) 
{	//传入指向通讯录的头指针的指针
	char name[NAME_MAX];	//获取要删除的联系人的姓名
	printf("请输⼊要删除的⽤⼾姓名:\n");
	scanf("%s", name);
	contact* pos = FindByName(*con, name); //定义一个pos指针来接受找到的联系人地址
	if (pos == NULL) 
	{
		printf("要删除的用户不存在,删除失败!\n");
		return;
	}
	SLTErase(con, pos);		//联系人存在则使用链表的SLTErase函数进行删除
	printf("删除成功!\n");
}
2.2.5 显示联系人
void ShowContact(contact* con) 	//传入一个指向通讯录头部的指针 con 
{
	printf("%-10s %-4s %-4s %15s %-20s\n", "姓名", "性别", "年龄", "联系电话", "地址");
		contact * cur = con;	//定义一个指针cur指向联系人结构体指针
	while (cur)			//通过循环遍历打印通讯录
	{
		printf("%-10s %-4s %-4d %15s %-20s\n",
			cur->data.name,
			cur->data.sex,
			cur->data.age,
			cur->data.tel,
			cur->data.addr);
		cur = cur->next;
	}
}

2.2.6 查找联系人
void FindContact(contact* con) 	//传入一个指向通讯录头部的指针 con 
{
	char name[NAME_MAX];	//获取要查找的联系人的姓名
	printf("请输⼊要查找的用户姓名:\n");
	scanf("%s", name);
	contact* pos = FindByName(con, name);	//通过FindByName函数来寻找
	if (pos == NULL) 
	{
		printf("要查找的用户不存在,查找失败!\n");
		return;
	}
	printf("查找成功!\n");
	printf("%-10s %-4s %-4d %15s %-20s\n",
		pos->data.name,
		pos->data.sex,
		pos->data.age,
		pos->data.tel,
		pos->data.addr);
}
2.2.7 修改联系人
void ModifyContact(contact** con) 
{	//传入指向通讯录的头指针的指针
	char name[NAME_MAX];
	printf("请输⼊要修改的⽤⼾名称:\n");
	scanf("%s", &name);
	contact* pos = FindByName(*con, name);
	if (pos == NULL) 
	{
		printf("要查找的⽤⼾不存在,修改失败!\n");
		return;
	}
	printf("请输⼊要修改的姓名:\n");
	scanf("%s", pos->data.name);
	printf("请输⼊要修改的性别:\n");
	scanf("%s", pos->data.sex);
	printf("请输⼊要修改的年龄:\n");
	scanf("%d", &pos->data.age);
	printf("请输⼊要修改的联系电话:\n");
	scanf("%s", pos->data.tel);
	printf("请输⼊要修改的地址:\n");
	scanf("%s", pos->data.addr);
	printf("修改成功!\n");
}
2.2.8 保存通讯录到文件中
void SaveContact(contact* con) 
{	//传入一个指向通讯录头部的指针 con 
	FILE* pf = fopen("contact.txt", "wb");
	//打开一个名为 "contact.txt" 的文件,以二进制写入模式打开,并将文件指针赋值给 pf。
	if (pf == NULL) 
	{
		perror("fopen error!\n");
		return;
	}
	//将通讯录数据写⼊⽂件
	contact* cur = con;	//定义一个指针cur指向联系人结构体指针
	while (cur)
	{
		fwrite(&(cur->data), sizeof(cur->data), 1, pf);
		//在循环中,使用 fwrite 函数将当前联系人的数据写入文件。
		//&(cur->data) 是对当前联系人数据的地址的引用,
		//sizeof(cur->data) 表示要写入的数据大小,
		//1 表示要写入的数据项数量,pf 是文件指针,指定写入的目标文件。
		cur = cur->next;
	}
	printf("通讯录数据保存成功!\n");
}
2.2.9 销毁通讯录
void DestroyContact(contact** con) 
{	//传入指向通讯录的头指针的指针
	SaveContact(*con);
	//在通讯录销毁之前,先把历史数据保存到本地⽂件中contact.txt
	SListDesTroy(con);	//调用链表中的SListDesTroy函数来销毁通讯录
}
2.2.10 通讯录菜单
#define _CRT_SECURE_NO_WARNINGS 1
#include"contact.h"
#include"SList.h"

void menu() 
{
	//通讯录初始化
	contact* con = NULL;
	InitContact(&con);
	int op = -1;
	do {
		printf("*********************************\n");
		printf("*****1、添加用户 2、删除用户*****\n");
		printf("*****3、查找用户 4、修改用户*****\n");
		printf("*****5、展示用户 0、退出 ********\n");
		printf("*********************************\n");
		printf("请选择您的操作:\n");
		scanf("%d", &op);
		switch (op)
		{
		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;
		default:
			printf("输⼊有误,请重新输⼊\n");
			break;
		}
	} while (op != 0);
	//销毁通讯录
	DestroyContact(&con);
}
int main()
{
	menu();
	return 0;
}

3. 代码总览

test.c 文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"contact.h"
#include"SList.h"

void menu() {
	//通讯录初始化
	contact* con = NULL;	
	InitContact(&con);
	int op = -1;
	do {
		printf("*********************************\n");
		printf("*****1、添加用户 2、删除用户*****\n");
		printf("*****3、查找用户 4、修改用户*****\n");
		printf("*****5、展示用户 0、退出 ********\n");
		printf("*********************************\n");
		printf("请选择您的操作:\n");
		scanf("%d", &op);
		switch (op)
		{
		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;
		default:
			printf("输⼊有误,请重新输⼊\n");
			break;
		}
	} while (op != 0);
	//销毁通讯录
	DestroyContact(&con);
}
int main()
{
	menu();
	return 0;
}

SList.c 文件

#include"SList.h"

单链表的打印
//void SLTPrint(SLTNode* phead)	//将单链表的头节点指针传入
//{
//	SLTNode* pcur = phead;		//定义一个pcur指针来接收头节点的地址
//	while (pcur)	//pcur != NULL    //如果链表不为空则进入循环遍历打印节点
//	{
//		printf("%d->", pcur->data);	//打印节点的数据
//		pcur = pcur->next;			//指向下一个节点
//	}
//	printf("NULL\n");				//链表全部打印完,最后一个节点的next指针指向NULL。
//}

//单链表节点的创建
SLTNode* SLTBuyNode(SLTDataType x)	//	将节点的数据传入,返回一个节点的指针
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));	//通过malloc函数向内存中申请一块连续可用的空间,并将地址返回newnode节点
	if (newnode == NULL)			//检查节点空间是否申请成功
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;				//存入节点数据
	newnode->next = NULL;			//并将节点的next指针指向NULL

	return newnode;					//返回节点指针
}


//单链表的尾插法
void SLTPushBack(SLTNode** pphead, SLTDataType x)	//传入指向头节点指针的指针和节点数据
{
	assert(pphead);						//传入的指针不能为空
	SLTNode* newnode = SLTBuyNode(x);	//创建一个节点,并定义一个指针newnode指向该节点
										//*pphead 就是指向第一个节点的指针
	if (*pphead == NULL)				//如果头节点指针指向NULL,证明该链表为空
	{
		*pphead = newnode;				//将新创建的节点作为该链表的头节点
	}
	else								//如果链表不为空,就要找到该链表的尾节点进行插入操作
	{
		SLTNode* ptail = *pphead;		//创建一个指针ptail来接收头结点的地址
		while (ptail->next)				//只要指针指向的节点的下一个节点不为空就进入循环
		{
			ptail = ptail->next;		//通过遍历找到尾节点
		}
		//循环结束后,ptail指向的就是尾结点
		ptail->next = newnode;			//让尾节点的next指针指向要插入的节点
	}
}


//单链表的头插法
void SLTPushFront(SLTNode** pphead, SLTDataType x)//传入指向头节点指针的指针和节点数据
{
	assert(pphead);								//传入的指针不能为空
	SLTNode* newnode = SLTBuyNode(x);			//创建一个节点,并定义一个指针newnode指向该节点
	newnode->next = *pphead;					//新创建的节点的next指针指向头结点
	*pphead = newnode;							//然后将新节点作为头结点
}


//单链表的尾删法
void SLTPopBack(SLTNode** pphead)		//传入指向头节点指针的指针
{
	assert(pphead && *pphead); 			//链表不能为空
	//如果链表只有一个节点
	if ((*pphead)->next == NULL) //	-> 优先级高于*,所以*pphead要用()
	{
		free(*pphead);					//释放掉指针并置空
		*pphead = NULL;
	}
	else {		//如果链表有多个节点

		SLTNode* prev = *pphead;			//定义两个指针指向头节点
		SLTNode* ptail = *pphead;
		while (ptail->next != NULL)					//只要ptail的next不为空就进入循环
		{
			prev = ptail;					//通过两个指针找尾节点,当ptail的next为空时,循环停止
			ptail = ptail->next;			//此时,ptail指向要删除的尾节点,prev指向倒数第二个节点
		}
		free(ptail);						//将ptail指针释放掉并置为空
		ptail = NULL;
		prev->next = NULL;					//通过将prev的next指针指向的节点置为空,来达到删除节点的目的
	}
}


//单链表的头删法
void SLTPopFront(SLTNode** pphead)		//传入指向头节点指针的指针
{
	assert(pphead && *pphead);			//链表不能为空
	//	-> 优先级高于*
	SLTNode* next = (*pphead)->next;	//定义一个next指针并指向要删除的头节点的下一个节点
	free(*pphead);						//释放点头节点达到删除节点的目的
	*pphead = next;						//将刚才的第二个节点设为头节点
}


//单链表的在指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{			//传入指向头节点指针的指针,指定位置的指针,要插入的数据
	assert(pphead && *pphead);			//链表不能为空
	assert(pos);						//指定的位置不能为空
	SLTNode* newnode = SLTBuyNode(x);	//创造一个新节点并插入数据
	if (pos == *pphead)					//若pos == *pphead,说明是头插
	{
		SLTPushFront(pphead, x);		//直接调用头插函数即可
	}
	else
	{
		SLTNode* prev = *pphead;		//定义一个指针prev指向头结点
		while (prev->next != pos)		//通过循环遍历找到指定的位置
		{
			prev = prev->next;			//循环结束时,prev指向的节点就是要进行尾插的节点
		}
		newnode->next = pos;			//尾插
		prev->next = newnode;
	}
}



//单链表的在指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{			//传入指定位置的指针,要插入的数据
	assert(pos);						//指定的位置不能为空
	SLTNode* newnode = SLTBuyNode(x);	//创造一个新节点并插入数据
	newnode->next = pos->next;			//在指定位置直接进行尾插
	pos->next = newnode;
}



//单链表的删除指定位置的节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{			//传入指向头节点指针的指针,指定位置的指针
	assert(pphead && *pphead);		//链表不能为空
	assert(pos);					//指定的位置不能为空
	//判断pos是头结点/pos不是头结点
	if (pos == *pphead)
	{
		//头删
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;	//定义一个指针prev指向头结点
		while (prev->next != pos)	//通过循环遍历找到指定的位置
		{
			prev = prev->next;		//循环结束时,prev指向的节点就是要进行尾删前面的节点
		}
		prev->next = pos->next;		//使要删除的节点的前面节点指向要尾删的后面节点
		free(pos);					//删除节点
		pos = NULL;
	}
}


//单链表的删除指定位置之后的节点
void SLTEraseAfter(SLTNode* pos)	//传入指定位置的指针
{
	assert(pos && pos->next);		//指定位置和其下一个节点都不能为空
	SLTNode* del = pos->next;		//定义一个del指针指向指定位置之后的节点
	pos->next = del->next;			//使要删除的节点的前面节点指向要尾删的后面节点
	free(del);						//删除节点
	del = NULL;
}


单链表的查找
//SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
//{			//传入指向链表头节点指针,和要查找的节点
//	SLTNode* pcur = phead;		//定义一个指针prev指向头结点
//	while (pcur)//等价于pcur != NULL
//	{
//		if (pcur->data == x)	//通过循环遍历寻找x节点
//		{
//			return pcur;
//		}
//		pcur = pcur->next;
//	}
//	//如果pcur == NULL了,依然没有返回,说明链表中没有该节点			
//	return NULL;
//}



//单链表的销毁
void SListDesTroy(SLTNode** pphead)	//传入指向头节点指针的指针
{
	assert(pphead && *pphead);		//头节点和指向头节点的指针都不能为空
	SLTNode* pcur = *pphead;		//定义一个指针pcur指向头结点
	while (pcur)					//通过循环遍历链表的每个节点并释放
	{
		SLTNode* 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"contact.h"

typedef struct PersonInfo SLTDataType;

typedef struct SListNode {
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

//void SLTPrint(SLTNode* phead);

//尾插
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);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);

//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);

//销毁链表
void SListDesTroy(SLTNode** pphead);

contact.c 文件

#include"contact.h"
#include"SList.h"
#include<string.h>
void LoadContact(contact** con)	//传入指向通讯录的头指针的指针
{
	FILE* pf = fopen("contact.txt", "rb");
	//打开一个名为 "contact.txt" 的文件,以二进制只读模式打开,并将文件指针赋值给pf。
	if (pf == NULL)
	{			//文件指针不能为空
		perror("fopen error!\n");
		return;
	}
	//循环读取文件数据
	PeoInfo info;	//声明一个名为 info 的结构体变量,用于存储从文件中读取的单个联系人信息。
	while (fread(&info, sizeof(info), 1, pf))
	{
		SLTPushBack(con, info);
	}
	printf("历史数据导入通讯录成功!\n");
}
//在循环中,使用 fread 函数从文件中读取 sizeof(info) 大小的数据到 info 变量中,如果读取成功,则调用 SLTPushBack 函数将 info 中的信息添加到通讯录链表的末尾。

void InitContact(contact** con) {
	LoadContact(con);//把本地的通讯录数据导⼊到链表结构
}


void AddContact(contact** con)
{
	//传入指向通讯录的头指针的指针
	PeoInfo info;
	//声明一个名为 info 的结构体变量,用于存储联系人信息。
	printf("请输入姓名:\n");
	scanf("%s", info.name);
	printf("请输入性别:\n");
	scanf("%s", info.sex);
	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[])
{
	//返回一个指向通讯录里联系人结构体的指针。该函数接受两个参数:一个指向通讯录头部的指针 con 和一个代表要查找的姓名的字符数组 name。
	contact* cur = con;	//定义一个指针cur指向联系人结构体指针
	while (cur)			//通过循环遍历整个通讯录链表
	{					//使用strcmp函数进行匹配
		if (strcmp(cur->data.name, name) == 0) {
			return cur;	//匹配成功返回指向当前联系人的指针
		}
		cur = cur->next;
	}
	return NULL;
}

void DelContact(contact** con)
{	//传入指向通讯录的头指针的指针
	char name[NAME_MAX];	//获取要删除的联系人的姓名
	printf("请输入要删除的用户姓名:\n");
	scanf("%s", name);
	contact* pos = FindByName(*con, name); //定义一个pos指针来接受找到的联系人地址
	if (pos == NULL)
	{
		printf("要删除的用户姓名不在,删除失败!\n");
		return;
	}
	SLTErase(con, pos);		//联系人存在则使用链表的SLTErase函数进行删除
	printf("删除成功!\n");
}


void ShowContact(contact* con) 	//传入一个指向通讯录头部的指针 con 
{
	printf("%-10s %-4s %-4s %15s %-20s\n", "姓名", "性别", "年龄", "联系电话", "地址");
	contact* cur = con;	//定义一个指针cur指向联系人结构体指针
	while (cur)			//通过循环遍历打印通讯录
	{
		printf("%-10s %-4s %-4d %15s %-20s\n",
			cur->data.name,
			cur->data.sex,
			cur->data.age,
			cur->data.tel,
			cur->data.addr);
		cur = cur->next;
	}
}

void FindContact(contact* con) 	//传入一个指向通讯录头部的指针 con 
{
	char name[NAME_MAX];	//获取要查找的联系人的姓名
	printf("请输入要查找的用户姓名:\n");
	scanf("%s", name);
	contact* pos = FindByName(con, name);	//通过FindByName函数来寻找
	if (pos == NULL)
	{
		printf("要查找的用户名不存在,查找失败!\n");
		return;
	}
	printf("查找成功!\n");
	printf("%-10s %-4s %-4d %15s %-20s\n",
		pos->data.name,
		pos->data.sex,
		pos->data.age,
		pos->data.tel,
		pos->data.addr);
}

void ModifyContact(contact** con)
{	//传入指向通讯录的头指针的指针
	char name[NAME_MAX];
	printf("请输入要修改的用户姓名:\n");
	scanf("%s", &name);
	contact* pos = FindByName(*con, name);
	if (pos == NULL)
	{
		printf("要修改的用户名不存在,修改失败!\n");
		return;
	}
	printf("请输入要修改的姓名:\n");
	scanf("%s", pos->data.name);
	printf("请输入要修改的性别:\n");
	scanf("%s", pos->data.sex);
	printf("请输入要修改的年龄:\n");
	scanf("%d", &pos->data.age);
	printf("请输入要修改的联系电话:\n");
	scanf("%s", pos->data.tel);
	printf("请输入要修改的地址:\n");
	scanf("%s", pos->data.addr);
	printf("修改成功!\n");
}


void SaveContact(contact* con)
{	//传入一个指向通讯录头部的指针 con 
	FILE* pf = fopen("contact.txt", "wb");
	//打开一个名为 "contact.txt" 的文件,以二进制写入模式打开,并将文件指针赋值给 pf。
	if (pf == NULL)
	{
		perror("fopen error!\n");
		return;
	}
	//将通讯录数据写⼊⽂件
	contact* cur = con;	//定义一个指针cur指向联系人结构体指针
	while (cur)
	{
		fwrite(&(cur->data), sizeof(cur->data), 1, pf);
		//在循环中,使用 fwrite 函数将当前联系人的数据写入文件。
		//&(cur->data) 是对当前联系人数据的地址的引用,
		//sizeof(cur->data) 表示要写入的数据大小,
		//1 表示要写入的数据项数量,pf 是文件指针,指定写入的目标文件。
		cur = cur->next;
	}
	printf("通讯录数据保存成功\n");
}


void DestroyContact(contact** con)
{	//传入指向通讯录的头指针的指针
	SaveContact(*con);
	//在通讯录销毁之前,先把历史数据保存到本地⽂件中contact.txt
	SListDesTroy(con);	//调用链表中的SListDesTroy函数来销毁通讯录
}

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);
  • 35
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值