您现在的位置是:首页 >技术交流 >动态通讯录实现(C语言)网站首页技术交流

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

派小星233 2024-06-17 11:28:05
简介动态通讯录实现(C语言)

目录

前言:

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

结点设计

主逻辑

二:接口实现

(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
");
		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	%-5s	%-5s	%-20s	%-20s
", "名字", "年龄", "性别", "电话", "地址");
	while (phead != NULL)
	{
		printf("%-20s	%-5d	%-5s	%-20s	%-20s
", 
			   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("查找失败
");
		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("删除成功
");
}

图解:

(6)修改信息

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

//修改信息
void ModifyContact(Node* phead)
{
	//查找
	char name[max_name];
	printf("请输入要修改联系人的名字:");
	scanf("%s", name);
	Node* pos = FindContact(phead, name);
	if (pos == NULL)
	{
		printf("查找失败
");
		return;
	}
	else
	{
		printf("找到了,进行修改
");
		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;
		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
");
		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("打开文件失败
");
		exit(-1);
	}

	//保存读取到的数据
	PepInfo* tmp = (PepInfo*)malloc(sizeof(PepInfo));
	if (tmp == NULL)
	{
		printf("malloc error
");
		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
");
			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
");
		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	%-5s	%-5s	%-20s	%-20s
", "名字", "年龄", "性别", "电话", "地址");
	while (phead != NULL)
	{
		printf("%-20s	%-5d	%-5s	%-20s	%-20s
", 
			   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("查找失败
");
		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("删除成功
");
}

//修改信息
void ModifyContact(Node* phead)
{
	//查找
	char name[max_name];
	printf("请输入要修改联系人的名字:");
	scanf("%s", name);
	Node* pos = FindContact(phead, name);
	if (pos == NULL)
	{
		printf("查找失败
");
		return;
	}
	else
	{
		printf("找到了,进行修改
");
		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
");
//		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("打开文件失败
");
		exit(-1);
	}

	//保存读取到的数据
	PepInfo* tmp = (PepInfo*)malloc(sizeof(PepInfo));
	if (tmp == NULL)
	{
		printf("malloc error
");
		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
");
			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("********************************
");
	printf("***** 1.增加      2.删除  ******
");
	printf("***** 3.查找      4.修改  ******
");
	printf("***** 5.排序      6.打印  ******
");
	printf("***** 0.退出              ******
");
	printf("********************************
");

}

int main()
{
	Node* head = NULL;
	int input = 0;

	//原信息录入,这里用读模式打开,第一次需要手动创建文件
	StartFileEntry(&head);
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			AddContact(&head);
			printf("增加成功
");
			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("查找失败
");
			}
			else
			{
				printf("找到了
");
				printf("%-20s	%-5d	%-5s	%-20s	%-20s
",
					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("排序成功
");
			break;
		case 6:
			PrintContact(head);
			break;
		case 0:
			printf("退出
");
			break;
		default:
			printf("非法输入,重新输入
");
			break;
		}
	} while (input);

	//新信息录入
	UpdateFileEntry(head);
	return 0;
}

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。