C语言:基于单链表实现通讯录项目

前言

之前我们用顺序表实现了通讯录,这次我们使用单链表实现。我们定义五个文件,

SingleLinkedList.h   SingleLinkedList.c  Contact.h Contact.c test.c

SingleLinkedList.h 是包含了单链表的结构,及各类库文件声明,各个单链表接口声明

SingleLinkedList.c 用于实现各类单链表接口

Contact.h是包含了通讯录的结构,各个通讯录接口声明

Contact.c用于实现通讯录接口

test.c用作测试和实现通讯录的菜单,控制增删查改的选项

SingleLinkedList.h文件

在我的上一篇博客中已经实现过单链表了,所以这里不作实现讲解

SingleLinkedList.h文件:
#define _CRT_SECURE_NO_WARNINGS	1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include"Contact.h"
#include"string.h"
#include<windows.h>
typedef PeoInfo SLLDataType;
typedef struct _SingleLinkedListNode
{
	SLLDataType x;
	struct _SingleLinkedListNode* next;
}SLLNode;
//获取一个新节点
SLLNode* BuynewNode(SLLDataType x);
//尾插
void SLLPushBack(SLLNode** pphead, SLLDataType x);
//头插
void SLLPushFront(SLLNode** pphead, SLLDataType x);
//尾删
void SLLPopBack(SLLNode** pphead);
//头删
void SLLPopFront(SLLNode** pphead);
//查找
SLLNode* SLLFind(SLLNode* phead, SLLDataType x);
//在指定位置之前插入数据
void SLLInsert(SLLNode** pphead, SLLNode* pos, SLLDataType x);
//在指定位置之后插入数据
void SLLInsertAfter(SLLNode* pos, SLLDataType x);
//删除指定位置之后的数据
void SLLEraseAfter(SLLNode* pos);
//删除指定位置的数据
void SLLErase(SLLNode** pphead, SLLNode* pos);
//打印链表
void SLLPrint(SLLNode* phead);
//销毁链表
void SLLDestroy(SLLNode** pphead);

SingleLinkedList.c文件

​
SingleLinkedList.c文件:
#include"SingleLinkedList.h"
//打印链表
//void SLLPrint(SLLNode* phead)
//{
//	SLLNode* pcur = phead;
//	while (pcur)
//	{
//		printf("%d->", pcur->x);
//		pcur = pcur->next;
//	}
//	printf("NULL\n");
//}
//获取一个新节点
SLLNode* BuynewNode(SLLDataType x)
{
	SLLNode* newnode = (SLLNode*)malloc(sizeof(SLLNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->x = x;
	newnode->next = NULL;
	return newnode;
}
//尾插
void SLLPushBack(SLLNode** pphead, SLLDataType x)
{
	assert(pphead);
	SLLNode* newnode = BuynewNode(x);
	if (*pphead == NULL)
	{

		*pphead = newnode;
	}
	else
	{
		SLLNode* ptail = *pphead;
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		ptail->next = newnode;
	}

}
//头插
void SLLPushFront(SLLNode** pphead, SLLDataType x)
{
	assert(pphead);
	SLLNode* newnode = BuynewNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}
//尾删
void SLLPopBack(SLLNode** pphead)
{
	assert(pphead && *pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLLNode* prev = *pphead;
		SLLNode* ptail = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		prev->next = NULL;
		free(ptail);
		ptail = NULL;
	}

}
//头删
void SLLPopFront(SLLNode** pphead)
{
	assert(pphead && *pphead);
	SLLNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}
//查找
//SLLNode* SLLFind(SLLNode* phead, SLLDataType x)
//{
//	assert(phead);
//	SLLNode* pcur = phead;
//	while (pcur)
//	{
//		if (pcur->x == x)
//		{
//			return pcur;
//		}
//		pcur = pcur->next;
//	}
//	return NULL;
//}
//在指定位置之前插入数据
void SLLInsert(SLLNode** pphead, SLLNode* pos, SLLDataType x)
{
	assert(pphead && *pphead);
	assert(pos);
	SLLNode* newnode = BuynewNode(x);
	if (*pphead == pos)
	{
		SLLPushFront(pphead, x);
	}
	else
	{
		SLLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		newnode->next = pos;
		prev->next = newnode;
	}

}
//在指定位置之后插入数据
void SLLInsertAfter(SLLNode* pos, SLLDataType x)
{
	assert(pos);
	SLLNode* newnode = BuynewNode(x);
	SLLNode* next = pos->next;

	newnode->next = next;
	pos->next = newnode;
}
//删除指定位置之后的数据
void SLLEraseAfter(SLLNode* pos)
{
	assert(pos && pos->next);
	SLLNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}
//删除指定位置的数据
void SLLErase(SLLNode** pphead, SLLNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SLLPopFront(pphead);
	}
	else
	{
		SLLNode* del = pos;
		SLLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = del->next;
		free(pos);
		pos = NULL;
	}

}
//销毁链表
void SLLDestroy(SLLNode** pphead)
{
	assert(pphead && *pphead);
	while (*pphead)
	{
		SLLNode* next = (*pphead)->next;
		free(*pphead);
		*pphead = next;
	}

}

​

这里我们把影响Contact.c文件的部分接口注释掉。

Contact.h文件

#pragma once
#define NAME_MAX 100
#define GENDER_MAX 10
#define TEL_MAX 19
#define ADDR_MAX 100
enum
{
    EXIT ,
    ADD,
    ERASE,
    FIND,
    MODIFY,
    SHOW
};
//前置声明
typedef struct _SingleLinkedListNode Contact;
//用户数据
typedef struct PersonInfo
{
    char name[NAME_MAX];
    char gender[GENDER_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文件各类接口实现

初始化通讯录

头文件声明:
void InitContact(Contact** con);
实现:
//初始化通讯录
void InitContact(Contact** con)
{
	assert(con);
	(*con) = NULL;
	PeoInfo pi;
	strcpy(pi.name,"NULL");
	strcpy(pi.gender, "NULL");
	pi.age = 0;
	strcpy(pi.tel, "NULL");
	strcpy(pi.addr, "NULL");
   SLLPushBack(con, pi);
	
}

字符数组用strcpy拷贝值,整型数据直接赋值

初始化:将数据都置为空,这样做的目的是为了再后期在通讯录没有数据的时候其他功能不会被断言报错,看一下后期实现的结果:


添加通讯录数据

头文件声明:
void AddContact(Contact** con);
实现:
//添加通讯录数据
void AddContact(Contact** con)
{
	assert(con);
	PeoInfo pi;
	printf("请输入要添加的联系人姓名:>\n");
	scanf("%s",pi.name);
	printf("请输入要添加的联系人性别:>\n");
	scanf("%s", pi.gender);
	printf("请输入要添加的联系人年龄:>\n");
	scanf("%d", &pi.age);
	printf("请输入要添加的联系人电话:>\n");
	scanf("%s", pi.tel);
	printf("请输入要添加的联系人家庭住址:>\n");
	scanf("%s", pi.addr);
     free(*con);
	*con = NULL;
	SLLPushBack(con,pi);

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

我们先定义一个联系人类的自定义结构体pi,将pi的各个数据填充好后,调用之前写好的尾插方法,尾插到通讯录(链表)中在尾插前,可以看到我先写了个*con=NULL,因为我之前初始化的时候,将字符串"NULL",先插入了通讯录中,只是为了后期不影响其他功能,而不能让真正意义上的通讯录打印出来的第一条数据是“NULL”,所以这里将链表先释放再置为NULL,然后再填充数据

后期的结果我们来看看:

没有数据前的通讯录:

添加数据后的通讯录:


删除通讯录数据

头文件声明:
void DelContact(Contact** con);
实现:
//删除通讯录数据
void DelContact(Contact** con)
{
	assert(con && *con);
	char name[NAME_MAX] = { 0 };
	Contact* pcur = *con;
	printf("请输入要删除的联系人姓名:>\n");
	scanf("%s", name);
	int flag = 0;
	while (pcur)
	{
		if (strcmp(name, pcur->x.name) == 0)
		{
			flag = 1;
			SLLErase(con, pcur);
			break;
		}
		else
		{
			pcur = pcur->next;
		}	
	}
	if (flag == 1)
	{
		printf("删除联系人成功!\n");
	}
	else
	{
		printf("要删除的联系人不存在\n");
	}
	
}

首先,我们定义一个字符数组,一个临时指针pcur指向*con,定义一个标志flag,当flag为1时,代表有这个数据,flag=0时代表,通讯录没有这个数据。然后输入一个姓名与我们通讯录中存在的姓名比较,如果二者相等就将flag置为1否则就不改动

SLLErase(con, pcur);

这个接口是我们之前写好的,用来删除pos位置的节点,如果找到了,那么就将这个pos位置的节点删除,那么从而实现通讯录的数据删除

来看看后期实现的效果:

删除前:

删除后:


展示通讯录数据

头文件声明:
void ShowContact(Contact* con);
实现:
//展示通讯录数据
void ShowContact(Contact* con)
{
	Contact* tmp = con;
	printf("        联系人信息\n");
	int i = 1;
	printf("序号 ""姓名 " "性别 " "年龄 " "电话 " "家庭住址\n");
	while (tmp)
	{
		
		printf(" %d %s  %s  %d  %s  %s ",i++, tmp->x.name,
			tmp->x.gender,
			tmp->x.age, tmp->x.tel,
			tmp->x.addr);
		tmp = tmp->next;
		printf("\n");
	}
	
}

实现效果:



查找通讯录数据

头文件声明:
void FindContact(Contact* con);
实现:
//查找通讯录数据
void FindContact(Contact* con)
{
	assert(con);
	char name[NAME_MAX] = { 0 };
	Contact* pcur = con;
	printf("请输入要查找的联系人姓名:>\n");
	scanf("%s", name);
	int flag = 0;
	while (pcur)
	{
		if (strcmp(name, pcur->x.name) == 0)
		{
			flag = 1;
			break;
		}
		else
		{
			pcur = pcur->next;
		}
	}
	if (flag == 1)
	{
		printf("%s的信息如下:>\n", pcur->x.name);
		printf( "姓名 " "性别 " "年龄 " "电话 " "家庭住址\n");
		printf(" %s  %s  %d  %s  %s ", pcur->x.name,
			pcur->x.gender,
			pcur->x.age, pcur->x.tel,
			pcur->x.addr);
		printf("\n");
	}
	else
	{
		printf("%s不在通讯录中\n",name);
	}
}

类似于删除操作,我们要遍历通讯录,然后找到那个对应的pcur指针,然后将pcur指针指向的结构体数据打印出来

实现效果:


 

修改通讯录数据

头文件声明:
void ModifyContact(Contact** con);
实现:
//修改通讯录数据
void ModifyContact(Contact** con)
{
	assert(con&&*con);
	printf("请输入要修改的联系人姓名:\n");
	char name[NAME_MAX] = { 0 };

	Contact* pcur = *con;
	scanf("%s", name);
	int flag = 0;
	while (pcur)
	{
		if (strcmp(name, pcur->x.name) == 0)
		{
			flag = 1;
			break;
		}
		else
		{
			pcur = pcur->next;
		}
	}
	if (flag == 1)
	{
		printf("请输入要修改的联系人姓名:>\n");
		scanf("%s", pcur->x.name);
		printf("请输入要修改的联系人性别:>\n");
		scanf("%s", pcur->x.gender);
		printf("请输入要修改的联系人年龄:>\n");
		scanf("%d", &pcur->x.age);
		printf("请输入要修改的联系人电话:>\n");
		scanf("%s", pcur->x.tel);
		printf("请输入要修改的联系人家庭住址:>\n");
		scanf("%s", pcur->x.addr);
		printf("修改成功!\n");
		printf("\n");
	}
	else
	{
		printf("%s不在通讯录中\n", name);
	}
}

实现效果:



销毁通讯录数据

头文件声明:
void DestroyContact(Contact** con);
实现:
//销毁通讯录数据
void DestroyContact(Contact** con)
{
	SLLDestroy(con);
}

通讯录的销毁就是链表的销毁,所以我们直接调用我们之前写好的销毁链表接口。

补充说明

char name[NAME_MAX] = { 0 };

	Contact* pcur = *con;
	scanf("%s", name);
	int flag = 0;
	while (pcur)
	{
		if (strcmp(name, pcur->x.name) == 0)
		{
			flag = 1;
			break;
		}
		else
		{
			pcur = pcur->next;
		}
	}

这块代码出现了很多次,其实可以封装成一个函数,多个函数进行调用,代码就不会那么冗余

完整Contact.c文件源码

#include"SingleLinkedList.h"
//初始化通讯录
void InitContact(Contact** con)
{
	assert(con);
	(*con) = NULL;
	PeoInfo pi;
	strcpy(pi.name,"NULL");
	strcpy(pi.gender, "NULL");
	pi.age = 0;
	strcpy(pi.tel, "NULL");
	strcpy(pi.addr, "NULL");
   SLLPushBack(con, pi);
	
}
//添加通讯录数据
void AddContact(Contact** con)
{
	assert(con);
	PeoInfo pi;
	printf("请输入要添加的联系人姓名:>\n");
	scanf("%s",pi.name);
	printf("请输入要添加的联系人性别:>\n");
	scanf("%s", pi.gender);
	printf("请输入要添加的联系人年龄:>\n");
	scanf("%d", &pi.age);
	printf("请输入要添加的联系人电话:>\n");
	scanf("%s", pi.tel);
	printf("请输入要添加的联系人家庭住址:>\n");
	scanf("%s", pi.addr);
	free(*con);
	*con = NULL;
	SLLPushBack(con,pi);

	printf("添加联系人信息成功!\n");
}
//展示通讯录数据
void ShowContact(Contact* con)
{
	Contact* tmp = con;
	printf("        联系人信息\n");
	int i = 1;
	printf("序号 ""姓名 " "性别 " "年龄 " "电话 " "家庭住址\n");
	while (tmp)
	{
		
		printf(" %d %s  %s  %d  %s  %s ",i++, tmp->x.name,
			tmp->x.gender,
			tmp->x.age, tmp->x.tel,
			tmp->x.addr);
		tmp = tmp->next;
		printf("\n");
	}
	
}
//删除通讯录数据
void DelContact(Contact** con)
{
	assert(con && *con);
	char name[NAME_MAX] = { 0 };
	Contact* pcur = *con;
	printf("请输入要删除的联系人姓名:>\n");
	scanf("%s", name);
	int flag = 0;
	while (pcur)
	{
		if (strcmp(name, pcur->x.name) == 0)
		{
			flag = 1;
			SLLErase(con, pcur);
			break;
		}
		else
		{
			pcur = pcur->next;
		}	
	}
	if (flag == 1)
	{
		printf("删除联系人成功!\n");
	}
	else
	{
		printf("要删除的联系人不存在\n");
	}
	
}
//查找通讯录数据
void FindContact(Contact* con)
{
	assert(con);
	char name[NAME_MAX] = { 0 };
	Contact* pcur = con;
	printf("请输入要查找的联系人姓名:>\n");
	scanf("%s", name);
	int flag = 0;
	while (pcur)
	{
		if (strcmp(name, pcur->x.name) == 0)
		{
			flag = 1;
			break;
		}
		else
		{
			pcur = pcur->next;
		}
	}
	if (flag == 1)
	{
		printf("%s的信息如下:>\n", pcur->x.name);
		printf( "姓名 " "性别 " "年龄 " "电话 " "家庭住址\n");
		printf(" %s  %s  %d  %s  %s ", pcur->x.name,
			pcur->x.gender,
			pcur->x.age, pcur->x.tel,
			pcur->x.addr);
		printf("\n");
	}
	else
	{
		printf("%s不在通讯录中\n",name);
	}
}
//修改通讯录数据
void ModifyContact(Contact** con)
{
	assert(con&&*con);
	printf("请输入要修改的联系人姓名:\n");
	char name[NAME_MAX] = { 0 };

	Contact* pcur = *con;
	scanf("%s", name);
	int flag = 0;
	while (pcur)
	{
		if (strcmp(name, pcur->x.name) == 0)
		{
			flag = 1;
			break;
		}
		else
		{
			pcur = pcur->next;
		}
	}
	if (flag == 1)
	{
		printf("请输入要修改的联系人姓名:>\n");
		scanf("%s", pcur->x.name);
		printf("请输入要修改的联系人性别:>\n");
		scanf("%s", pcur->x.gender);
		printf("请输入要修改的联系人年龄:>\n");
		scanf("%d", &pcur->x.age);
		printf("请输入要修改的联系人电话:>\n");
		scanf("%s", pcur->x.tel);
		printf("请输入要修改的联系人家庭住址:>\n");
		scanf("%s", pcur->x.addr);
		printf("修改成功!\n");
		printf("\n");
	}
	else
	{
		printf("%s不在通讯录中\n", name);
	}
}
//销毁通讯录数据
void DestroyContact(Contact** con)
{
	SLLDestroy(con);
}

test.c文件逻辑实现

#include"SingleLinkedList.h"
void test01()
{
	Contact* con;
	InitContact(&con);
	AddContact(&con);
	AddContact(&con);
	//DelContact(&con);
	//FindContact(con);
	//ModifyContact(&con);
	ShowContact(con);
    DestroyContact(&con);
	ShowContact(con);
}


int main()
{
	test01();

	return 0;
}

定义一个test01函数将各类通讯录接口测试一下,没有问题,我们开始写通讯录调用逻辑及菜单打印的实现:

menu()菜单函数实现

void menu()
{
	printf("          ---------我的通讯录---------\n");
	printf("          |1.添加联系人||2.删除联系人|\n");
	printf("          ----------------------------\n");
	printf("          |3.查找联系人||4.修改联系人|\n");
	printf("          ----------------------------\n");
	printf("          |5.展示联系人||0.退出      |\n");
	printf("          ----------------------------\n");
}

实现效果:

main()主函数逻辑实现

int main()
{
	int input = 0;
	Contact* con;
	InitContact(&con);
	do
	{
		menu();
		printf("请选择:>\n");
		scanf("%d", &input);
		system("cls");
		switch (input)
		{
		case EXIT:
			exit(0);
		case ADD:
			AddContact(&con);
			system("pause");
			system("cls");
			break;
		case ERASE:
			DelContact(&con);
			system("pause");
			system("cls");
			break;
		case FIND:
			FindContact(con);
			system("pause");
			system("cls");
			break;
		case MODIFY:
			ModifyContact(&con);
			system("pause");
			system("cls");
			break;
		case SHOW:
			ShowContact(con);
			system("pause");
			system("cls");
			break;
		}
	} while (input);
	DestroyContact(&con);
	return 0;
}

添加system("pause");和system("cls");程序暂停和程序清理语句,让各类接口衔接连贯,最后循环结束,将通讯录销毁,程序return 0正常退出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值