学习单链表时的懒人做法—>给每一条代码加注释
注:还在学习阶段,可能代码会有些许错误,如果大佬们看出来了记得评论帮我纠正过来(蟹蟹!!!)
注:代码注释是按照我自己学习时的理解来加的,所以不同思路的人看这些注释时会有不一样的理解,比如说代码中的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;
}
下次见@