数据结构单链表

目录

1. 前言

2. 链表的概念及结构

2.1 链表的概念

 2.2 链表的结构

3. 链表在内存中的存储

4. 链表的分类

4.1 单向和双向

4.2 带头和不带头

4.3 循环和不循环 

 4.4 单链表和双向链表

5. 单链表节点的定义

6. 单链表的实现

6.1 单链表的打印

6.2 单链表尾插

 6.3 单链表头插

6.4 单链表尾删

 6.5 单链表头删

6.6 查找单链表

6.6 在指定位置之前插入数据

 6.7 在指定位置之后插入数据

 6.8 删除指定位置数据

 6.9 删除指定位置之后的数据

 6.10 删除指定位置之前的数据

6.11 单链表销毁

 7. 完整代码

7.1 SListNode.h

7.2 SListNode.c

8. 链表实现通讯录完整代码

8.1 Contact.h

8.2 Contact.c

8.3 test.c
​​​​​​​​​​​​​​


1. 前言

关于顺序表的问题及思考

1. 在我们顺序表的中间或者头部插入删除数据的时候,会涉及到需要移动数据,时间复杂度为O(N)

2.顺序表的增容需要申请新的空间,拷贝数据到新空间,然后释放旧的空间,会有不小的消耗

3.增容我们一般是呈2倍的增长,势必后面会有一定的空间浪费,假设当前空间容量为100,满了之后需要进行增容,增加到200,但是我们只需要插入5个数据,后面没有数据需要插入了,这时就会浪费掉95个数据空间

面对这样的一系列的问题,这时,我们链表就可以很好的解决掉顺序表存在的一系列问题。

本章主要讲链表中的单链表

2. 链表的概念及结构

2.1 链表的概念

概念:链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的之间链接次序实现的

 2.2 链表的结构

链表是由一个一个的节点组成的,其中节点我们在之前学习自定义类型结构体的自引用的时候简单提到过,节点是由数据和指针组成的,其中,指针是指向下一个节点的指针。

可能光有说明不太好理解,我们通过图形来结合理解。

这里的1,2,3,4都是结构体,也被称之为节点,我们通过指向第一个结构体的指针phead找到第一个结构体,然后通过第一个结构体里面存的指针找到结构体,通过结构体里面存的指向下一个结构体的指针依次寻找,直到找到最后一个结构体,最后一个结构体里面存放的是空指针就不用往后找了,这样我们就可以遍历完链表。

3. 链表在内存中的存储

链表里面的每一个节点都是独立动态申请的一块空间,所以链表是存储在堆区的,链表的每个节点都是单独申请的空间,所以在内存中可能是连续存储的,也可能是不连续的,与顺序表不同,顺序表是连续存储的。

4. 链表的分类

4.1 单向和双向

4.2 带头和不带头

4.3 循环和不循环 

 4.4 单链表和双向链表

链表的结构样式有非常多种,以上的这些样式结合起来就有8中链表结构。

虽然有这么多的链表结构,但是我们实际中常用的链表只有两种链表结构,单链表(不带头单向不循环)和双向链表(带头双向循环)。

单链表: 结构简单,一般不会单独用来存数据,实际中更多是作为其他数据结构的子结构,如哈希桶,图的邻接表等等,另外这种结构在笔试面试中出现很多。

双向链表:结构最复杂,一般用在单独存放数据,实际中使用的链表数据结构,都是带头双向循环链表,另外这个结构复杂,但是在代码实现以后我们会发现结构会带来很多优势,实际反而更简单了,后续代码实现了就知道了。

本章主要讲述链表结构中的单链表

5. 单链表节点的定义

我们知道节点是由数据和指向下一个节点的指针组成

struct SListNode
{
	int data;//数据
	struct SListNode* next;//指向下一个节点的指针
};

6. 单链表的实现

我们还是和实现顺序表一样,多文件操作,单链表头文件SListNode.h,单链表源文件SListNode.c和测试文件test.c

SListNode.h

//下面是需要使用到的库函数的头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int SLNDataType;//类型重命名,方便后续替换

typedef struct SListNode //类型重命名为sln
{
	SLNDataType data;//数据
	struct SListNode* next;//指向下一个节点的指针
}sln;

//单链表打印
void SLNPrint(sln* phead);
//单链表尾插
void SLNPushBack(sln** pphead, SLNDataType x);
//单链表头插
void SLNPushFront(sln** pphead, SLNDataType x);
//单链表尾删
void SLNPopBack(sln** pphead);
//单链表头删
void SLNPopFront(sln** pphead);
//查找单链表
sln* SLNFind(sln* phead, SLNDataType x);
//在指定位置之前插入数据
void* SLNInsert(sln** pphead, sln* pos, SLNDataType x);
//在指定位置之后插入数据
void* SLNInsertAfter(sln* pos, SLNDataType x);
//删除指定位置数据
void* SLNDel(sln** pphead, sln* pos);
//删除指定位置之后的数据
void* SLNEraseAfter(sln* pos);
//删除指定位置之前的数据
void* SLNErase(sln** pphead, sln* pos);
//销毁单链表
void* SLNDestroy(sln** pphead);

6.1 单链表的打印

思路:

我们在之前讲述了,链表里的节点里面有存储指向下一个节点的指针,那我们就进行循环打印,就可以了。

 SListNode.c

void SLNPrint(sln* phead)
{
	sln* pcur = phead;//定义一个同类型的指针接收phead
	while (pcur)//判断pcur是否是空指针
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;//让pcur走向下一个节点
	}
	printf("NULL\n");
}

test.c

#include"SListNode.h"

void test()
{
	sln* plist = NULL;//创建单链表变量
	SLNPrint(plist);//打印链表
}
int main()
{
	test();
	return 0;
}

测试一下打印,当前我们单链表并没有插入任何数据,所以链表为空

6.2 单链表尾插

思路:

在我们单链表进行尾插的时候,我们需要动态申请一个节点,然后插入到单链表的后面,但是我们要考虑一下链表是否是空链表,如果是空链表,那么链表的头节点就是新插入的这个节点,如果不是空链表,那么我们需要定义一个指针,用来寻找单链表的尾节点,然后将尾节点的指向修改指向新申请的节点,这样单链表的尾删就完成了。

SListNode.c

//申请节点函数
sln* SLNBuyNode(SLNDataType x)
{
	sln* newnode = (sln*)malloc(sizeof(sln));//动态申请空间
	if (newnode == NULL)//判断申请是否成功
	{
		perror("malloc");//打印错误信息
		exit(1);//退出程序
	}
	//申请成功
	newnode->data = x;
	newnode->next = NULL;
	return newnode;//返回申请的新节点
}
void SLNPushBack(sln** pphead, SLNDataType x)
{
	assert(pphead);//判断pphead是否为空指针
	sln* newnode = SLNBuyNode(x);//调用节点申请函数
	if (*pphead == NULL)//判断链表是否为空
	{  //如果为空,此时链表的头节点就是申请的新节点
		*pphead = newnode;
	}
	else
	{ //如果不为空 我们需要创建一个指针,来寻找链表的尾节点
		sln* ptail = *pphead;
		while (ptail->next)//循环进行查找
		{
			ptail = ptail->next;
		}
		//将尾节点的指针指向修改为指向新申请的节点
		ptail->next = newnode;
	}
}

 test.c

void test()
{
	sln* plist = NULL;//创建单链表变量
	//我们尾插4个数据进去
	SLNPushBack(&plist, 1);//这里plist是指针,为什么需要取地址呢?
	SLNPushBack(&plist, 2);//因为plist是指针变量,我们需要修改形参影响实参
	SLNPushBack(&plist, 3);//那么我们就需要取出指针变量的地址
	SLNPushBack(&plist, 4);
	SLNPrint(plist);//打印链表
}

测试单链表尾插

 6.3 单链表头插

思路:

单链表头插,就是在单链表的前面进行插入,我们首先要申请节点,然后就新申请的节点的指向修改为指向头节点,然后将新节点变成头节点。这样单链表的头插就完成了

SListNode.c

void SLNPushFront(sln** pphead, SLNDataType x)
{
	assert(pphead);//判断pphead是否为空指针
	sln* newnode = SLNBuyNode(x);//申请节点
	//我们将新节点的指向修改为指向头节点的下一个节点
	newnode->next = *pphead;
	//然后将头节点赋值为新节点
	*pphead = newnode;
}

测试单链表头插

6.4 单链表尾删

思路:

对单链表进行尾删,我们首先要判断链表是否为空链表,然后再判断,链表里面只有一个节点,或者多个节点的情况,如果直接一个节点,直接将他释放,如果有多个节点,我们定义两个指针,一个用来找尾节点,一个用来找尾节点的前一个节点,找到尾节点之后,将尾节点释放,然后将尾节点的前一个节点指向空,这样单链表尾删就完成了。

SListNode.c

void SLNPopBack(sln** pphead)
{
	assert(pphead && *pphead);//判断pphead是否是空指针,链表是否是空链表
	//判断链表里是否只有一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else 
	{
		//定义一个同类型指针变量,用来找尾节点
		sln* ptail = *pphead;
		//定义一个同类型指针变量,用来找尾节点的前一个节点
		sln* prev = *pphead;
		while (ptail->next)//遍历链表
		{
			prev = ptail;
			ptail = ptail->next;
		}
		free(ptail);//释放掉尾节点
		prev->next = NULL;//将尾节点前一个节点指向空
	}
}

测试尾删代码

void SLNPopFront(sln** pphead)
{
	assert(pphead && *pphead);//判断pphead是否是空指针,链表是否是空链表
	//我们首先创一个指针用来接收头节点的下一个节点
	sln* next = (*pphead)->next;
	free(*pphead);//释放掉头节点
	*pphead = next;//然后将头节点指向的下一个节点变成头节点
}

 6.5 单链表头删

思路:

对单链表进行头删,我们首先要判断链表是否为空,然后创建一个指针,指向头节点的下一个节点,然后删除头节点,最后将头节点指向的下一个节点变成头节点,这样单链表的头删就完成了。

SListNode.c

void SLNPopFront(sln** pphead)
{
	assert(pphead && *pphead);//判断pphead是否是空指针,链表是否是空链表
	//我们首先创一个指针用来接收头节点的下一个节点
	sln* next = (*pphead)->next;
	free(*pphead);//释放掉头节点
	*pphead = next;//然后将头节点指向的下一个节点变成头节点
}

测试单链表头删

6.6 查找单链表

思路:

查找单链表,我们需要创建一个指针来遍历单链表,如果查找到了,返回当前节点,如果没有查找到,返回空指针

SListNode.c

sln* SLNFind(sln* phead, SLNDataType x)
{
	assert(phead);//判断phead是否为空指针
	//定义一个指针用来遍历链表
	sln* pfind = phead;
	while (pfind)
	{
		if (pfind->data == x)
		{
			//找到了,返回节点
			return pfind;
		}
		pfind= pfind->next;//让节点往后走
	}
	//出循环代表没有找到,返回一个空指针
	return NULL;
}

测试代码查找

6.6 在指定位置之前插入数据

思路:

我们在指定位置之前插入数据,首先判断链表是否为空链表,然后再判断插入位置是否合法,当我们要指定的位置是头节点的时候,我们直接调用头插代码,如果是其他位置,我们先创建一个新节点,然后创建一个指向pos前一个节点的指针,将新节点指向pos,将pos前一个节点指向新节点,这样在指定位置之前插入数据就完成了

SListNode.c

void* SLNInsert(sln** pphead, sln* pos, SLNDataType x)
{
	assert(pphead && *pphead);//判断pphead是否为空指针,链表是否为空
	assert(pos);//判断pos是否为空指针
	//如果pos是头节点,直接调用头插
	if (pos == *pphead)
	{
		SLNPushFront(pphead,x);
	}
	else
	{
		//创建新节点
		sln* newnode = SLNBuyNode(x);
		//创建一个指针用来找pos的前一个节点
		sln* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//将新节点指向pos
		newnode->next = pos;
		//将pos的前一个节点指向新节点
		prev->next = newnode;
	}
}

测试在指定位置之前插入数据

 6.7 在指定位置之后插入数据

思路:

首先,我们创建一个新节点来接收数据,然后将新节点指向pos的下一个节点,再将pos指向新节点,这样在指定位置之后插入数据就完成了

SListNode.c

void* SLNInsertAfter(sln* pos, SLNDataType x)
{
	assert(pos);//判断pos是否为空指针
	//创建一个节点接收数据
	sln* newnode = SLNBuyNode(x);
	//将新节点指向pos的下个节点
	newnode->next = pos->next;
	//将pos指向新节点
	pos->next = newnode;
}

测试在指定位置之后插入数据

 6.8 删除指定位置数据

思路:

我们删除指定位置,首先要判断链表是否是空链表,并且指定位置是否合法。如果指定位置是头节点,那我们直接调用头删代码,如果在其他位置,我们创建一个指针找pos的前一个节点,然后将pos前一个节点指向pos的下一个节点,最后释放pos,将pos置为空,这样在指定位置删除代码就完成了

SListNode.c

void* SLNDel(sln** pphead, sln* pos)
{
	assert(pphead && *pphead);//判断pphead是否为空指针,链表是否为空
	assert(pos);//判断pos是否为空指针
	//如果指定位置是头节点,调用头删
	if (pos == *pphead)
	{
		SLNPopFront(pos);
	}
	else
	{
		//创建一个指针找pos的前一个节点
		sln* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//将prev指向pos下一个节点
		prev->next = pos->next;
		//释放pos
		free(pos);
		pos = NULL;//防止pos变成野指针
	}
}

测试删除指定位置数据

 6.9 删除指定位置之后的数据

思路:

我们要删除指定位置之后的数据,首先我们要判断pos是否是空指针并且pos的下一个节点不能为空。我们先创建一个指针next,指向pos的下下个节点,然后释放掉pos的下一个节点,最后将pos指向next

SListNode.c

void* SLNEraseAfter(sln* pos)
{

	assert(pos&&pos->next);//判断pos是否为空指针,并且pos的下一个节点也不能为空
	//创建一个指针指向pos的下下个节点
	sln* next = pos->next->next;
	//释放掉pos的下一个节点
	free(pos->next);
	//将pos指向它的下下节点
	pos->next = next;
}

测试删除指定位置之后的数据

 6.10 删除指定位置之前的数据

思路:

我们要删除指定位置之前的数据,我们先判断链表是否为空,然后再判断pos是否是空指针,pos是否是头节点。如果头节点的下一个节点是pos,我们直接调用头删代码,如果不是

,我们创建两个指针,一个找pos前一个节点,另一个找pos前前一个节点,然后将pos前前节点指向pos,释放掉pos前一个节点,并置空,这样我们删除指定位置之前的数据就完成了

SListNode.c

void* SLNErase(sln** pphead, sln* pos)
{
	assert(pphead && *pphead);//判断pphead是否为空指针,链表是否为空
	assert(pos && pos != (*pphead));//判断pos是否是空指针,pos不能是头节点
	//如果头节点的下一个数据是pos,那我们直接调用头删代码
	if ((*pphead)->next == pos)
	{
		SLNPopFront(pphead);
	}
	else
	{
		//创建一个指针找pos的前一个节点
		sln* pcur = *pphead;
		//创建一个指针找pos的前前个节点
		sln* prev = *pphead;
		while (pcur->next != pos)
		{
			//这里先将prev指向pcur
			prev = pcur;
			//在挪动pcur,这样prev一直指向pcur前一个节点
			pcur = pcur->next;
		}
		//这里将prev指向pos
		prev->next = pos;
		//释放掉pos的前一个节点
		free(pcur);
		pcur = NULL;//防止野指针
	}
}

测试删除指定位置之前的数据

6.11 单链表销毁

思路:

我们单链表的节点是一个一个申请的,所以需要一个一个释放,我们创建一个指针变量来遍历链表,然后进行一个一个释放,最后将链表头节点置为空。

SListNode.c

void* SLNDestroy(sln** pphead)
{
	assert(pphead && *pphead);//判断pphead是否是空指针,链表是否为空
	//创建指针变量指向头节点
	sln* pcur = *pphead;
	//循环销毁
	while (pcur)
	{
		sln* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//最后将头节点置空
	*pphead = NULL;
}

测试单链表销毁

 7. 完整代码

7.1 SListNode.h

//下面是需要使用到的库函数的头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int SLNDataType;//类型重命名,方便后续替换

typedef struct SListNode //类型重命名为sln
{
	SLNDataType data;//数据
	struct SListNode* next;//指向下一个节点的指针
}sln;

//单链表打印
void SLNPrint(sln* phead);
//单链表尾插
void SLNPushBack(sln** pphead, SLNDataType x);
//单链表头插
void SLNPushFront(sln** pphead, SLNDataType x);
//单链表尾删
void SLNPopBack(sln** pphead);
//单链表头删
void SLNPopFront(sln** pphead);
//查找单链表
sln* SLNFind(sln* phead, SLNDataType x);
//在指定位置之前插入数据
void* SLNInsert(sln** pphead, sln* pos, SLNDataType x);
//在指定位置之后插入数据
void* SLNInsertAfter(sln* pos, SLNDataType x);
//删除指定位置数据
void* SLNDel(sln** pphead, sln* pos);
//删除指定位置之后的数据
void* SLNEraseAfter(sln* pos);
//删除指定位置之前的数据
void* SLNErase(sln** pphead, sln* pos);
//销毁单链表
void* SLNDestroy(sln** pphead);

7.2 SListNode.c

#include"SListNode.h"

void SLNPrint(sln* phead)
{
	sln* pcur = phead;//定义一个同类型的指针接收phead
	while (pcur)//判断pcur是否是空指针
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;//让pcur走向下一个节点
	}
	printf("NULL\n");
}

//申请节点函数
sln* SLNBuyNode(SLNDataType x)
{
	sln* newnode = (sln*)malloc(sizeof(sln));//动态申请空间
	if (newnode == NULL)//判断申请是否成功
	{
		perror("malloc");//打印错误信息
		exit(1);//退出程序
	}
	//申请成功
	newnode->data = x;
	newnode->next = NULL;
	return newnode;//返回申请的新节点
}

void SLNPushBack(sln** pphead, SLNDataType x)
{
	assert(pphead);//判断pphead是否为空指针
	sln* newnode = SLNBuyNode(x);//调用节点申请函数
	if (*pphead == NULL)//判断链表是否为空
	{  //如果为空,此时链表的头节点就是申请的新节点
		*pphead = newnode;
	}
	else
	{ //如果不为空 我们需要创建一个指针,来寻找链表的尾节点
		sln* ptail = *pphead;
		while (ptail->next)//循环进行查找
		{
			ptail = ptail->next;
		}
		//将尾节点的指针指向修改为指向新申请的节点
		ptail->next = newnode;
	}
}

void SLNPushFront(sln** pphead, SLNDataType x)
{
	assert(pphead);//判断pphead是否为空指针
	sln* newnode = SLNBuyNode(x);//申请节点
	//我们将新节点的指向修改为指向头节点的下一个节点
	newnode->next = *pphead;
	//然后将头节点赋值为新节点
	*pphead = newnode;
}

void SLNPopBack(sln** pphead)
{
	assert(pphead && *pphead);//判断pphead是否是空指针,链表是否是空链表
	//判断链表里是否只有一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else 
	{
		//定义一个同类型指针变量,用来找尾节点
		sln* ptail = *pphead;
		//定义一个同类型指针变量,用来找尾节点的前一个节点
		sln* prev = *pphead;
		while (ptail->next)//遍历链表
		{
			prev = ptail;
			ptail = ptail->next;
		}
		free(ptail);//释放掉尾节点
		prev->next = NULL;//将尾节点前一个节点指向空
	}
}

void SLNPopFront(sln** pphead)
{
	assert(pphead && *pphead);//判断pphead是否是空指针,链表是否是空链表
	//我们首先创一个指针用来接收头节点的下一个节点
	sln* next = (*pphead)->next;
	free(*pphead);//释放掉头节点
	*pphead = next;//然后将头节点指向的下一个节点变成头节点
}

sln* SLNFind(sln* phead, SLNDataType x)
{
	assert(phead);//判断phead是否为空指针
	//定义一个指针用来遍历链表
	sln* pfind = phead;
	while (pfind)
	{
		if (pfind->data == x)
		{
			//找到了,返回节点
			return pfind;
		}
		pfind= pfind->next;
	}
	//出循环代表没有找到,返回一个空指针
	return NULL;
}

void* SLNInsert(sln** pphead, sln* pos, SLNDataType x)
{
	assert(pphead && *pphead);//判断pphead是否为空指针,链表是否为空
	assert(pos);//判断pos是否为空指针
	//如果pos是头节点,直接调用头插
	if (pos == *pphead)
	{
		SLNPushFront(pphead,x);
	}
	else
	{
		//创建新节点
		sln* newnode = SLNBuyNode(x);
		//创建一个指针用来找pos的前一个节点
		sln* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//将新节点指向pos
		newnode->next = pos;
		//将pos的前一个节点指向新节点
		prev->next = newnode;
	}
}

void* SLNInsertAfter(sln* pos, SLNDataType x)
{
	assert(pos);//判断pos是否为空指针
	//创建一个节点接收数据
	sln* newnode = SLNBuyNode(x);
	//将新节点指向pos的下个节点
	newnode->next = pos->next;
	//将pos指向新节点
	pos->next = newnode;
}

void* SLNDel(sln** pphead, sln* pos)
{
	assert(pphead && *pphead);//判断pphead是否为空指针,链表是否为空
	assert(pos);//判断pos是否为空指针
	//如果指定位置是头节点,调用头删
	if (pos == *pphead)
	{
		SLNPopFront(pphead);
	}
	else
	{
		//创建一个指针找pos的前一个节点
		sln* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//将prev指向pos下一个节点
		prev->next = pos->next;
		//释放pos
		free(pos);
		pos = NULL;//防止pos变成野指针
	}
}

void* SLNEraseAfter(sln* pos)
{

	assert(pos&&pos->next);//判断pos是否为空指针,并且pos的下一个节点也不能为空
	//创建一个指针指向pos的下下个节点
	sln* next = pos->next->next;
	//释放掉pos的下一个节点
	free(pos->next);
	//将pos指向它的下下节点
	pos->next = next;
}

void* SLNErase(sln** pphead, sln* pos)
{
	assert(pphead && *pphead);//判断pphead是否为空指针,链表是否为空
	assert(pos && pos != (*pphead));//判断pos是否是空指针,pos不能是头节点
	//如果头节点的下一个数据是pos,那我们直接调用头删代码
	if ((*pphead)->next == pos)
	{
		SLNPopFront(pphead);
	}
	else
	{
		//创建一个指针找pos的前一个节点
		sln* pcur = *pphead;
		//创建一个指针找pos的前前个节点
		sln* prev = *pphead;
		while (pcur->next != pos)
		{
			//这里先将prev指向pcur
			prev = pcur;
			//在挪动pcur,这样prev一直指向pcur前一个节点
			pcur = pcur->next;
		}
		//这里将prev指向pos
		prev->next = pos;
		//释放掉pos的前一个节点
		free(pcur);
		pcur = NULL;//防止野指针
	}
}

void* SLNDestroy(sln** pphead)
{
	assert(pphead && *pphead);//判断pphead是否是空指针,链表是否为空
	//创建指针变量指向头节点
	sln* pcur = *pphead;
	//循环销毁
	while (pcur)
	{
		sln* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//最后将头节点置空
	*pphead = NULL;
}

8. 链表实现通讯录完整代码

在前面我们用顺序表实现了一个简单的通讯录,其实链表也是可以的

8.1 Contact.h

//下面是需要使用到的库函数的头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
//定义字符数组大小,方便后续替换
#define NAME_MAX 20
#define GENDER_MAX 20
#define Tel_MAX 20
#define ADDR_MAX 100


typedef struct Contaic
{
	char name[NAME_MAX];
	char gender[GENDER_MAX];
	int age;
	char tel[Tel_MAX];
	char addr[ADDR_MAX];
}sct;

typedef struct Contaic SLNDataType;//类型重命名,方便后续替换

typedef struct SListNode //类型重命名为sln
{
	SLNDataType data;//数据
	struct SListNode* next;//指向下一个节点的指针
}sln;

//通讯录展示
void ConShow(sln* phead);
//通讯录添加数据
void ConAdd(sln** pphead);
//通讯录删除数据
void* ConDel(sln** pphead);
//通讯录查找数据
void* ConFind(sln* phead);
//通讯录修改数据
void* ConModify(sln** pphead);
//通讯录销毁
void* SLNDestroy(sln** pphead);

8.2 Contact.c

#include"Contact.h"

sln* slnfind(sln* phead,char name[])
{
	sln* pfind = phead;
	while (pfind)
	{
		if (strcmp(pfind->data.name,name) == 0)
		{
			return pfind;
		}
		pfind = pfind->next;
	}
	return NULL;
}
void ConShow(sln* phead)
{
	sln* pcur = phead;
	printf("姓名 性别 年龄 电话 住址\n");
	while (pcur)
	{
		printf("%4s %4s %4d %4s %4s\n",
			pcur->data.name,
			pcur->data.gender,
			pcur->data.age,
			pcur->data.tel,
			pcur->data.addr);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

sln* SLNBuyNode(sct sct)
{
	sln* newnode = (sln*)malloc(sizeof(sln));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(1);
	}
	newnode->data = sct;
	newnode->next = NULL;
	return newnode;
}


void ConAdd(sln** pphead)
{
	assert(pphead);
	sct sct = { 0 };
	printf("请输入你要添加的联系人姓名!\n");
	scanf("%s",sct.name);
	printf("请输入你要添加的联系人性别!\n");
	scanf("%s",sct.gender);
	printf("请输入你要添加的联系人年龄!\n");
	scanf("%d",&(sct.age));
	printf("请输入你要添加的联系人电话!\n");
	scanf("%s",sct.tel);
	printf("请输入你要添加的联系人住址!\n");
	scanf("%s",sct.addr);
	sln* newnode = SLNBuyNode(sct);
	if (*pphead == NULL)
	{  
		*pphead = newnode;
	}
	else
	{ 
		sln* ptail = *pphead;
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		ptail->next = newnode;
	}
	printf("添加联系人成功!\n");
}

void* ConFind(sln* phead)
{
	assert(phead);
	sln* pfind = phead;
	char name[NAME_MAX];
	printf("请输入你要查找的联系人姓名!\n");
	scanf("%s",name);
	sln* find = slnfind(phead,name);
	if (find == NULL)
	{
		printf("你要查找的联系人不存在\n");
	}
	else
	{
		printf("姓名 性别 年龄 电话 住址\n");
		printf("%4s %4s %4d %4s %4s\n",
			find->data.name,
			find->data.gender,
			find->data.age,
			find->data.tel,
			find->data.addr
		);
	}
}

void* ConDel(sln** pphead)
{
	assert(pphead && *pphead);
	char name[NAME_MAX];
	printf("请输入你要删除的联系人姓名!\n");
	scanf("%s",name);
	sln* pdel = slnfind(*pphead,name);
	if(pdel == NULL)
	{
		printf("你要删除的联系人不存在\n");
	}
	else
	{
		if(pdel == *pphead)
		{
			sln* next = (*pphead)->next;
			free(*pphead);
			*pphead = next;
		}
		else
		{
			sln* prev = *pphead;
			while (prev->next != pdel)
			{
				prev = prev->next;
			}
			prev->next = pdel->next;
			free(pdel);
			pdel = NULL;
		}
		printf("删除成功!\n");
	}
}

void* ConModify(sln** pphead)
{
	assert(pphead && *pphead);
	char name[NAME_MAX];
	printf("请输入你要修改的联系人姓名!\n");
	scanf("%s",name);
	sln* pmodify = slnfind(*pphead,name);
	if (pmodify == NULL)
	{
		printf("你要修改的联系人不存在!\n");
	}
	else
	{
		printf("请输入你要新建的联系人姓名!\n");
		scanf("%s",pmodify->data.name);
		printf("请输入你要新建的联系人性别!\n");
		scanf("%s",pmodify->data.gender);
		printf("请输入你要新建的联系人年龄!\n");
		scanf("%d",&(pmodify->data.age));
		printf("请输入你要新建的联系人电话!\n");
		scanf("%s",pmodify->data.tel);
		printf("请输入你要新建的联系人住址!\n");
		scanf("%s",pmodify->data.addr);
	}
}

void* SLNDestroy(sln** pphead)
{
	assert(pphead && *pphead);
	sln* pcur = *pphead;
	while (pcur)
	{
		sln* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

8.3 test.c

#include"Contact.h"

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

}
int main()
{
	int input = 0;
	sln* plist = NULL;
	do
	{
		menu();
		printf("请选择你的操作!\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			ConAdd(&plist);
			break;
		case 2:
			ConDel(&plist);
			break;
		case 3:
			ConShow(plist);
			break;
		case 4:
			ConFind(plist);
			break;
		case 5:
			ConModify(&plist);
			break;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	} while (input);
	SLNDestroy(&plist);
	return 0;
}
  • 32
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值