一、具体代码
//头文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
typedef int SLDataType;
typedef struct SListNode
{
SLDataType data;
struct SListNode * pNext;
}SLNode, *pSLNode;
pSLNode Built_ListNode(SLDataType value);
void SList_Pushback(pSLNode *ppHead, SLDataType value);
void SList_Pushfront(pSLNode *pphead, SLDataType value);
void SList_Popfront(pSLNode *pphead);
void SList_Popback(pSLNode *pphead);
pSLNode SList_find(pSLNode phead, SLDataType value);
void SList_Insert(pSLNode *pphead, pSLNode pos, SLDataType value);
void SList_Earse(pSLNode *pphead, pSLNode pos);
void Print_List(pSLNode phead);
//main.c
#include "SList.h"
// 定义单链表节点结构体
typedef struct SLNode
{
SLDataType data; // 数据域
struct SLNode* pNext; // 指针域,指向下一个节点
}SLNode, *pSLNode;
// 创建一个新的节点并初始化
pSLNode Built_ListNode(SLDataType value)
{
pSLNode newnode = (pSLNode)malloc(sizeof(SLNode)); // 分配内存空间
newnode->data = value; // 设置数据域的值
newnode->pNext = NULL; // 设置指针域为NULL
return newnode; // 返回新节点的指针
}
// 在链表尾部插入节点
void SList_Pushback(pSLNode *ppHead, SLDataType value)
{
pSLNode newnode = Built_ListNode(value); // 创建新节点
if(*ppHead == NULL) // 若链表为空
{
*ppHead = newnode; // 新节点为链表的头节点
}
else
{
pSLNode ptail = *ppHead; // 遍历链表找到尾节点
while (ptail->pNext != NULL)
{
ptail = ptail->pNext;
}
ptail->pNext = newnode; // 将新节点连接到尾节点的后面
}
}
// 删除链表头部节点
void SList_Popfront(pSLNode *pphead)
{
pSLNode next = (*pphead)->pNext; // 保存头节点的下一个节点的指针
free(*pphead); // 释放头节点的内存空间
*pphead = next; // 将头节点的下一个节点作为新的头节点
}
// 删除链表尾部节点
void SList_Popback(pSLNode *pphead)
{
if(*pphead == NULL) // 链表为空
{
return;
}
else if((*pphead)->pNext == NULL) // 链表只有一个节点
{
free(*pphead); // 释放头节点的内存空间
*pphead = NULL; // 头节点指针置空
}
else
{
pSLNode prev = NULL; // 用于保存要删除节点的前一个节点的指针
pSLNode tail = *pphead; // 遍历链表找到尾节点
while (tail->pNext != NULL)
{
prev = tail; // 保存前一个节点的指针
tail = tail->pNext;
}
free(tail); // 释放尾节点的内存空间
prev->pNext = NULL; // 将前一个节点的指针域置空,表示删除尾节点
}
}
// 在链表头部插入节点
void SList_Pushfront(pSLNode *pphead, SLDataType value)
{
pSLNode newnode = Built_ListNode(value); // 创建新节点
newnode->pNext = *pphead; // 将新节点的下一个节点指向当前头节点
*pphead = newnode; // 将新节点设置为头节点
}
// 打印链表的所有节点值
void Print_List(pSLNode phead)
{
pSLNode cur = phead; // 从头节点开始遍历链表
while (cur != NULL)
{
printf("%d->", cur->data); // 打印当前节点的值
cur = cur->pNext; // 移动到下一个节点
}
printf("NULL\n"); // 链表打印完毕
}
// 在链表中查找指定值的节点
pSLNode SList_find(pSLNode phead, SLDataType value)
{
pSLNode cur = phead; // 从头节点开始遍历链表
while (cur != NULL)
{
if(cur->data == value) // 若当前节点的值等于要查找的值
{
return cur; // 返回当前节点的指针
}
cur = cur->pNext; // 移动到下一个节点
}
return NULL; // 链表中未找到指定值的节点
}
// 在指定位置插入节点
void SList_Insert(pSLNode *pphead, pSLNode pos, SLDataType value)
{
if(*pphead == pos) // 若要插入的位置为头节点
{
SList_Pushfront(pphead, value); // 调用头插函数
}
else
{
pSLNode newnode = Built_ListNode(value); // 创建新节点
pSLNode prev = *pphead; // 用于保存要插入位置的前一个节点的指针
while (prev->pNext != pos)
{
prev = prev->pNext; // 找到要插入位置的前一个节点
}
prev->pNext = newnode; // 将新节点插入到链表中
newnode->pNext = pos;
}
}
// 删除指定位置的节点
void SList_Earse(pSLNode *pphead, pSLNode pos)
{
if(*pphead == pos) // 若要删除的位置为头节点
{
SList_Popfront(pphead); // 调用头删函数
}
else
{
pSLNode prev = *pphead; // 用于保存要删除位置的前一个节点的指针
while (prev->pNext != pos)
{
prev = prev->pNext; // 找到要删除位置的前一个节点
}
prev->pNext = pos->pNext; // 将前一个节点的指针域连接到要删除节点的后一个节点
free(pos); // 释放要删除节点的内存空间
}
}
//代码功能测试
int main()
{
pSLNode plist = NULL; // 链表的头指针初始化为空
SList_Pushback(&plist, 1); // 在链表尾部插入节点
SList_Pushback(&plist, 2);
SList_Pushback(&plist, 3);
SList_Pushback(&plist, 4);
SList_Pushback(&plist, 5);
SList_Pushback(&plist, 6);
SList_Pushfront(&plist, 0); // 在链表头部插入节点
Print_List(plist); // 打印链表的所有节点值
SList_Popback(&plist); // 删除链表尾部节点
Print_List(plist);
pSLNode pos = SList_find(plist, 1); // 查找链表中值为1的节点
if(pos)
{
SList_Insert(&plist, pos, 10); // 在节点pos之前插入新节点
}
Print_List(plist);
pos = SList_find(plist, 0); // 查找链表中值为0的节点
if(pos)
{
SList_Earse(&plist, pos); // 删除节点pos
}
Print_List(plist);
return 0;
}
二、易错点分析
1、内存泄漏
// 在链表尾部插入节点
void SList_Pushback(pSLNode *ppHead, SLDataType value)
{
pSLNode newnode = Built_ListNode(value); // 创建新节点
if(*ppHead == NULL) // 若链表为空
{
*ppHead = newnode; // 新节点为链表的头节点
}
else
{
pSLNode ptail = *ppHead; // 遍历链表找到尾节点
while (ptail->pNext != NULL)
{
ptail = ptail->pNext;
}
ptail->pNext = newnode; // 将新节点连接到尾节点的后面
}
}
问题:
在该代码段中,如果链表为空,但未能将新节点设置为头节点时,导致无法成功将新节点连接到链表尾部,会导致新节点的内存泄漏。
解决方法:
加上链表为空的判断,如果链表为空直接将新的节点作为链表的头节点。
空指针引用:
// 删除链表尾部节点
void SList_Popback(pSLNode *pphead)
{
if(*pphead == NULL) // 链表为空
{
return;
}
else if((*pphead)->pNext == NULL) // 链表只有一个节点
{
free(*pphead); // 释放头节点的内存空间
*pphead = NULL; // 头节点指针置空
}
else
{
pSLNode prev = NULL; // 用于保存要删除节点的前一个节点的指针
pSLNode tail = *pphead; // 遍历链表找到尾节点
while (tail->pNext != NULL)
{
prev = tail; // 保存前一个节点的指针
tail = tail->pNext;
}
free(tail); // 释放尾节点的内存空间
prev->pNext = NULL; // 将前一个节点的指针域置空,表示删除尾节点
}
}
问题
在删除链表尾部节点时,未对空链表进行处理,可能会导致空指针引用错误。如果链表中只有一个节点时,会直接free 导致*pphead的指针没有置空,出现野指针的情况
解决方法
加上特殊情况的判断,如果链表为空直接return函数,如果链表中只有个节点时,直接将头指针置空。
边界条件处理:
// 在指定位置插入节点
void SList_Insert(pSLNode *pphead, pSLNode pos, SLDataType value)
{
if(*pphead == pos) // 若要插入的位置为头节点
{
SList_Pushfront(pphead, value); // 调用头插函数
}
else
{
pSLNode newnode = Built_ListNode(value); // 创建新节点
pSLNode prev = *pphead; // 用于保存要插入位置的前一个节点的指针
while (prev->pNext != pos)
{
prev = prev->pNext; // 找到要插入位置的前一个节点
}
prev->pNext = newnode; // 将新节点插入到链表中
newnode->pNext = pos;
}
}
问题
在插入节点时,如果插入位置为第一个位置时,由于prev->pNext会将第一个位置直接跳过,而导致无法找到第一个位置。
解决方法
在插入节点之前,应对特殊位置的节点进行相应的处理,并最好引入非法位置的判断。