2021-03-30 代码解析---单链表(数据结构)

学习单链表时的懒人做法—>给每一条代码加注释

注:还在学习阶段,可能代码会有些许错误,如果大佬们看出来了记得评论帮我纠正过来(蟹蟹!!!)

注:代码注释是按照我自己学习时的理解来加的,所以不同思路的人看这些注释时会有不一样的理解,比如说代码中的pTmp和pDel,它们实质上是结点指针,但是在注释中我可能会直接称之为结点pTmp或者pDel.

注:由于代码和注释太多时看起来不太方便,所以我敲代码的时候每个模块之间会有2-3 行的空行(甚至更多~~~~,but这不重要,只要代码能看懂就行)。

1.头文件 - - - >link.h

#ifndef __LINK_H__
#define __LINK_H__

#define HEAD 0
#define TAIL 1

//typedef的通俗作用:为现有类型创建别名,定义易于记忆的类型名
typedef int data_t;


//描述结点的结构体
typedef struct LinkNode
{
	data_t data;//data是结点的数据
	struct LinkNode * pNext;//pNext是指向下一个结点的指针
}LinkNode;



//描述单链表的结构体,只需要知道第一个结点的首地址即可
typedef struct Link
{
	LinkNode * pHead;//pHead是指向头结点的指针
	int count;//结点的个数
}Link;



//枚举定义
enum LINK_OP
{
	LINK_ERROR = -1,
	LINK_OK
};



//函数声明
Link * creatLink();//创建
int insertItemLink(Link * pLink,int iOffset,data_t tData );//插入
int deleteItemLink(Link * pLink,int iOffset,data_t * pData);//删除
int updateItemLink(Link * pLink,data_t oldData, data_t newData);//修改
int showLink(Link * pLink);//显示
void destroy(Link **ppLink);//销毁


#endif

2.封装的子函数- - - >link.c

#include <stdio.h>
#include <stdlib.h>//malloc(),free(),realloc(),calloc()
#include <string.h>//memset()
#include "link.h"//自定义头文件

/*
 *子函数:创建
 *功能:创建一个新链表,指向头结点的指针应该是NULL
 *参数:
 *返回值:pLink:指向链表的指针
*/
Link * creatLink()
{
	//开辟一片空间,首地址赋值给pLink
	Link * pLink = (Link *)malloc(sizeof(Link));

	//判断空间是否为空
	if(NULL == pLink)
	{
		return NULL;
	}

	//清空
	//memset(pLink,0,sizeof(Link));//给pLink指针指向的那片空间Link进行清空
	pLink->pHead = NULL; //此时链表还没有结点,所以pHead的初始值为NULL
	pLink->count = 0;  //此时结点个数为0
	return pLink; //返回值:链表首地址
}



/*
 * 子函数:插入
 *功能:插入结点
 *参数:pLink:要操作的单链表
 *   iOffset:要插入的位置,如果iOffset的值很大,就可以插在末尾
 *   tData:要插入的值
 *返回值:成功 LINK_OK
 *    失败 LINK_ERROR
*/
int insertItemLink(Link * pLink,int iOffset,data_t tData )
{
	//入参检查
	if(NULL == pLink || iOffset < -1 )
	{
		return LINK_ERROR;
	}

	//开始插入结点
	
	//1.创建一个结点
	LinkNode * pNode = (LinkNode *)malloc(sizeof(LinkNode));
	if( NULL == pNode)//判断新申请的结点是否为空
	{
		return LINK_ERROR;
	}

	//若该结点不为空,则对新申请的这片空间进行清空
	memset(pNode,0,sizeof(LinkNode));//pNode是指向新创建的结点的指针
	pNode->data = tData;//为新结点的数据域赋值tData


	//如果是空链表,需要单独进行插入
	if(NULL == pLink->pHead)
	{
		pLink->pHead = pNode;//空链表的头节点指针指向新申请的结点
		pLink->count++;//链表结点数+1

		return LINK_OK;
	}

	//不是空链表,将新创建的结点进行插入

	//创建一个结点指针先指向单链表的头结点
	LinkNode * pTmp = pLink->pHead;

	switch(iOffset)
	{
		//头插:插入头结点之前,插入结点的位置为0
	case HEAD:
		pNode->pNext = pLink->pHead;//结点pNode指向头指针
		pLink->pHead = pNode;//头指针指向pNode
		break;

		//尾插:插入原来的尾结点之后,成为新的尾结点
	case TAIL:
		printf("tail......\n");
		//通过指针判空遍历,先找到尾结点
		while(pTmp->pNext != NULL)
		{
			//当pTmp所指向的结点的next指针指向的区域不为空,就继续指向下一个结点
			pTmp = pTmp->pNext;
		}

		//循环结束后,pTmp就指向了最后一个结点,即此时pTmp->next=NULL
		pTmp->pNext = pNode;//让尾结点的next指针指向结点pNode
		break;
		
		//其他情况的插入,即中间插入
	default:
		{
			//移动pTmp,指向要插入位置的前一个位置
			int i;
			for(i=0;i < iOffset-1;i++)
			{
				//如果pTmp是最后一个结点,就不能再移动了
				if(NULL == pTmp->pNext)
				{
					break;//此时pTmp已经指向单链表的尾结点,不能再继续向后移动
				}
				//如果不是,则指针一直向后移动
				pTmp = pTmp->pNext;
			}

			//找到位置后,将pNode指向的结点插入链表
			//先连接后面的结点(这是为了防止链表中的结点数据丢失),再连接前面的结点
			pNode->pNext = pTmp->pNext;
			pTmp->pNext = pNode;
		}
		break;
	}

	//单链表中的结点数+1
	pLink->count++;

	return LINK_OK;
}



/*
 *子函数:删除
 *功能:删除链表中的结点
 *参数:pLink:链表地址
 *    iOffset:要删除的结点在链表中的地址
 *    pData:指针,用来保存要删除的结点中的数据
 *返回值:成功 LINK_OK
 *       失败 LINK_ERROR
*/
int deleteItemLink(Link * pLink,int iOffset,data_t * pData)
{
	//入参检查
	if(NULL == pLink || iOffset < 0 || iOffset >= pLink->count || NULL == pData)
	{
		return LINK_ERROR;
	}

	//找到要删除的结点的前一个节点
	//pTmp是要删除的节点的前一个节点
	//pDel:要删除的结点


	//如果只有一个结点,或者要删除的是头结点
	if( 1 == pLink->count || 0 == iOffset)
	{
		//指针pData指向的区域用来存放这个结点中的数据
		*pData = pLink->pHead->data;


		if(pLink->count > 1)//如果节点数大于1
		{
			LinkNode * pDel = pLink->pHead;
			pLink->pHead = pLink->pHead->pNext;
			free(pDel);
			pDel = NULL;
		}
		else
		{
			free(pLink->pHead);//直接释放头结点空间
			pLink->pHead = NULL;//头节点指针置空
		}

		//free(pLink->pHead);
		pLink->count--;

		return LINK_OK;
	}
	
	//如果链表中不止一个结点
	
	//让一个新的指针pTmp指向链表的头结点
	LinkNode * pTmp = pLink->pHead;

	//pTmp从链表的头结点开始遍历,直到到达要删除的结点的前一个结点的位置
	int i;
	for(i=0;i<iOffset-1;i++)
	{
		pTmp = pTmp->pNext;
	}

	//让一个新的指针指向要删除的结点
	LinkNode * pDel = pTmp->pNext;
	
	//结点指针pTmp指向的结点的pNext指针,指向结点指针pDel指向的结点的下一个结点
	pTmp->pNext = pDel->pNext;

	//用pData这个指针指向的空间存放将要删除的结点中的数据
	*pData = pDel->data;

	//释放指针pDel指向的结点空间
	free(pDel);

	//pDel指针置空
					/*为什么要进行指针的置空:释放指针指向的空间后,
					 * 指针本身的值并没有改变,无法通过指针来判断空
					 * 间是否被释放.此时指针所指向的内存空间仍然存
					 * 在,可以读到随机值,也可以继续为其赋值,与静
					 * 态内存相似.但是这块内存处于堆区,没有被指针
					 * 独占,随时会被其他动态申请的指针占用,若程序
					 * 退出损毁指针和释放内存容易造成堆损坏(内存不安全)
					*/
	pDel = NULL;


	//链表内的结点数-1
	pLink->count--;

	return LINK_OK;
}



/*
 *子函数:修改
 *功能:修改链表中某个结点的数据域
 *参数:pLink:链表地址
 *      oldData:原来的数据
 *      newData:修改后的数据
 *返回值:成功 LINK_OK
 *        失败 LINK_ERROR
*/
int updateItemLink(Link * pLink,data_t oldData, data_t newData)
{
	//对链表进行判空
	if(NULL == pLink)	
	{
		return LINK_ERROR;
	}

	//定义一个用来遍历的新指针指向链表的头结点
	LinkNode * pTmp = pLink->pHead;

	//用判空的方法进行结点的遍历
	while(pTmp != NULL)
	{
		if(pTmp->data == oldData)//当遍历到的结点的数据为oldData时
		{
			pTmp->data = newData;//为该结点的数据域赋值newData
		}

		//否则继续向后遍历,直到到达尾结点之后
		pTmp = pTmp->pNext;
	}

}



/*
 *子函数:打印
 *功能:将链表里各结点中的数据显示出来
 *参数:pLink:链表地址
 *返回值:成功 LINK_OK
 *       失败 LINK_ERROR
*/
int showLink(Link * pLink)
{
	//判空
	if(NULL == pLink)
	{
		return;	
	}

	//定义一个新的指向头结点的指针pTmp,该指针用来遍历链表
	LinkNode * pTmp = pLink->pHead;

	//通过判空使得结点指针从头至尾向后遍历,如果结点不为空则显示该节点中的数据
	while(pTmp != NULL)
	{
		printf("%d   ",pTmp->data);//打印数据
		pTmp = pTmp->pNext;//指向下一结点,即向后遍历
	}
	printf("\n\n");

	//返回值
	return LINK_OK;
}



/*
 *子函数:销毁
 *功能:销毁链表
 *参数:ppLink:指向 链表的指针 的指针
 *返回值:void
*/
void destroy(Link ** ppLink)
{
	/*指针判空,ppLink为指向链表指针的指针,
	  		   *ppLink为指向链表的指针,相当于pLink
			   (*ppLink)->head为头结点指针,相当于pLink->head
    */
	if(NULL == ppLink || NULL == *ppLink || NULL == (*ppLink)->pHead)
	{
		return;
	}
	
	//判空结束,开始删除链表
	//定义一个新的结点指针pTmp
	LinkNode * pTmp = NULL;

	//当头结点不为空时
	while( (*ppLink)->pHead != NULL)
	{
		pTmp = (*ppLink)->pHead;//让结点指针pTmp指向头结点
		(*ppLink)->pHead = (*ppLink)->pHead->pNext;//原本指向头结点的指针指向下一个结点,继续向后遍历
		free(pTmp);//释放结点指针pTmp指针指向的结点空间
	}//该方法为:利用头删法(从链表头部删除结点)销毁链表


	/*
	  当原本用来遍历链表的结点指针指向NULL时,
	  意味着整个链表已经遍历完,而整个链表里的结点空间也已经被释放结束
	  此时用来释放结点空间的结点指针pTmp完成使命
	*/

	//指针置空(指针必须置空的原因在子函数:deleteItemLink()函数中已经写明)
	pTmp = NULL;

	//释放链表空间
	free(*ppLink);

	//链表指针置空(指针置空原因同上)
	*ppLink = NULL;
}

3.主函数- - - >main.c

#include <stdio.h>
#include "link.h"//如果自定义的头文件不在当前路径,则需要加上该头文件的路径,例如:上一级文件夹中的.h文件,则为:#include "../link.h"

int main()
{
	/*创建链表*/
	Link * pLink = creatLink();
	//创建链表后进行判空
	if(NULL == pLink )
	{
		printf("链表创建失败!\n");
		return LINK_ERROR;
	}


	/*插入多个结点*/
	int i,j;
	printf("请输入要插入的结点个数:");
	scanf("%d",&i);
	for(j=0;j<i;j++)
	{
		insertItemLink(pLink,j,j+100);//参数:要操作的链表,要插入的位置,要插入的数据
	}
	printf("插入成功,此时链表里的数据为:");
	showLink(pLink);

	
	/*单独插入一个结点*/
	data_t data,iOffset;
	printf("请输入要插入的结点的位置和数据:");
	scanf("%d %d",&iOffset,&data);
	insertItemLink(pLink,iOffset,data);
	printf("插入成功,此时链表里的数据为:");
	showLink(pLink);


	/*删除结点*/
	printf("请输入要删除的结点的位置:");
	scanf("%d",&iOffset);
	deleteItemLink(pLink,iOffset,&data);//要操作的链表,要删除的节点的位置,用来保存要删除的结点中的数据
	printf("删除第%d个结点后,此时链表中的数据为:",iOffset);
	showLink(pLink);


	/*修改*/
	data_t oldData,newData;
	printf("请输入进行修改的新数据和旧数据:");
	scanf("%d %d",&oldData,&newData);
	updateItemLink(pLink,oldData,newData);
	printf("经过修改后的链表数据为:");
	showLink(pLink);


	/*销毁链表*/
	destroy(&pLink);//传给子函数的参数是链表pLink的地址

	return 0;
}

下次见@

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值