通讯录(基于单链表)

通讯录(基于单链表)

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

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

// 要在链表的每一个节点中存储联系人数据 
// 那我们就要定义联系人的结构体
#define NAME_MAX 20
#define GENDER_MAX 10
#define TEL_MAX 20
#define ADDR_MAX 100

typedef 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->pos
		prev->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 100

typedef 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;
		}
		else
			printf("输入不合法,请重新输入\n");
	}

	DestroyContact(&con);// 将通讯录销毁
	return 0;
}

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

  • 10
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值