目录
一:核心操作及代码要点
1.在指定位置之前插入数据
全部代码如下:
cpp
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLDataType x);
cpp
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLDataType x)
{
assert(pphead && *pphead);
assert(pos);
//先调用申请节点空间的函数
SLTNode* newnode = SLTBuyNode(x);
//如果pos==*pphead 则说明是头部插入
if (pos == *pphead)
{
SLTPushFront(pphead, x);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
//此时prev->newnode->pos
newnode->next = pos;
prev->next = newnode;
}
}
cpp
SLTNode* find = SLTFind(plist, 1);
SLTInsert(&plist, find, 11);
SLTPrint(plist);
输出结果如下:

解析:
函数声明
cpp
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLDataType x);
-
参数1 :
SLTNode** pphead------ 二级指针,指向链表的头指针 -
参数2 :
SLTNode* pos------ 指定位置的节点指针(在这个节点之前插入) -
参数3 :
SLDataType x------ 要插入的数据 -
返回值 :
void -
作用 :在链表中指定的
pos节点之前插入一个新节点
为什么用二级指针? 因为如果 pos 是头节点,插入后头指针需要更新。
函数头
cpp
void SLTInsert(SLTNode** phead, SLTNode* pos, SLDataType x)
- 注意参数名是
phead(二级指针)
断言检查
cpp
assert(phead && *phead);
assert(pos);
-
第一行:链表不能为空(
phead有效且*phead不为 NULL) -
第二行:
pos不能为空,必须是一个有效的节点指针 -
注意 :这里假设
pos一定是链表中的节点,函数不会验证这一点
判断是否为头插
cpp
//如果pos==*phead则说明是头部插入
if (pos == *phead)
SLTPushFront(phead, x);
-
如果
pos指向头节点,那么"在pos之前插入"就是头插 -
直接复用之前写好的
SLTPushFront函数 -
注意传的是
phead(二级指针)和x
非头插情况
cpp
else
{
SLTNode* prev = *phead;
while (prev->next != pos)
{
prev = prev->next;
}
//此时prev->newnode->pos
newnode->next = pos;
prev->next = newnode;
}
找pos的前一个节点
cpp
SLTNode* prev = *phead;
while (prev->next != pos)
{
prev = prev->next;
}
-
从头开始遍历,找到
next指向pos的那个节点 -
循环条件:
prev->next != pos,当prev的下一个节点不是pos时继续 -
循环结束时,
prev就是pos的前一个节点
插入新节点
cpp
newnode->next = pos; // 新节点指向pos
prev->next = newnode; // 前一个节点指向新节点
-
这两步的顺序不能颠倒
-
先让新节点指向
pos -
再让前一个节点指向新节点
插入过程图解
情况1:pos不是头节点
假设链表 1->2->3->4->NULL,要在 3 之前插入 11:
初始状态:
1\|●\]──→\[2\|●\]──→\[3\|●\]──→\[4\|NULL
↑
pos
第一步:找prev
prev从1开始:
prev=1: 1->next=2 ≠ 3 → prev=2
prev=2: 2->next=3 == pos → 停止
此时 prev 指向节点2
第二步:newnode->next = pos
1\|●\]──→\[2\|●\]──→\[3\|●\]──→\[4\|NULL
↑ ↑
│ pos
└──[11|●]──┘
newnode
第三步:prev->next = newnode
1\|●\]──→\[2\|●\]──→\[11\|●\]──→\[3\|●\]──→\[4\|NULL
最终结果:1->2->11->3->4->NULL
情况2:pos是头节点
假设链表 1->2->3->NULL,要在 1 之前插入 11:
if (pos == *phead) 成立
直接调用 SLTPushFront(phead, 11)
结果:11->1->2->3->NULL
2.在指定位置之后插入数据
全部代码如下:
cpp
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLDataType x);
cpp
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLDataType x)
{
assert(pos);
//先调用申请节点空间的函数
SLTNode* newnode = SLTBuyNode(x);
//此时我们需 pos->newnode-> pos->next
newnode->next = pos->next;
pos->next = newnode;
}
cpp
//在指定位置之后插入数据
SLTNode* find = SLTFind(plist, 1);
SLTInsertAfter(find, 100);
SLTPrint(plist);
输出结果如下:

解析:
函数声明
cpp
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLDataType x);
-
参数1 :
SLTNode* pos------ 一级指针,指定位置的节点 -
参数2 :
SLDataType x------ 要插入的数据 -
返回值 :
void -
作用 :在链表中指定的
pos节点之后插入一个新节点
为什么用一级指针?
因为在节点之后插入,不需要修改头指针。即使 pos 是最后一个节点,插入后也只是修改 pos->next,头指针不变
核心操作(两步)
cpp
newnode->next = pos->next; // 第一步:新节点指向pos的下一个节点
pos->next = newnode; // 第二步:pos指向新节点
这两步的顺序至关重要:
-
必须先执行第一步,让新节点先链接到后面的节点
-
再执行第二步 ,让
pos指向新节点
插入过程图解
情况1:pos在中间
假设链表 1->2->3->4->NULL,要在节点 2 之后插入 100:
初始状态:
1\|●\]──→\[2\|●\]──→\[3\|●\]──→\[4\|NULL
↑
pos
第一步:newnode->next = pos->next
newnode pos->next
100\|●\]────→\[3\|●\]──→\[4\|NULL
↑
新节点指向节点3
第二步:pos->next = newnode
1\|●\]──→\[2\|●\]──→\[100\|●\]──→\[3\|●\]──→\[4\|NULL
↑
pos指向新节点
最终结果:1->2->100->3->4->NULL
情况2:pos在末尾
假设链表 1->2->3->4->NULL,要在节点 4 之后插入 100:
初始状态:
1\|●\]──→\[2\|●\]──→\[3\|●\]──→\[4\|NULL
↑
pos
第一步:newnode->next = pos->next
newnode pos->next = NULL
100\|●\]────→NULL 第二步:pos-\>next = newnode \[1\|●\]──→\[2\|●\]──→\[3\|●\]──→\[4\|●\]──→\[100\|NULL
↑
pos指向新节点
最终结果:1->2->3->4->100->NULL(尾插效果)
3.删除pos节点
全部代码如下:
cpp
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
cpp
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
assert(pos);
//pos是头节点 以及 pos不是节点的情况
if (pos == *pphead)
{
//头删
SLTPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
//此时找到了 prev pos pos->next
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
cpp
////删除pos节点
SLTNode* find = SLTFind(plist, 2);
SLTErase(&plist,find);
SLTPrint(plist);
输出结果如下:

解析:
处理头节点删除
cpp
//pos是头节点 以及 pos不是节点的情况
if (pos == *pphead)
{
//头删
SLTPopFront(pphead);
}
-
如果
pos指向头节点,直接复用之前写好的SLTPopFront函数 -
SLTPopFront已经处理了释放内存和更新头指针
找pos的前一个节点
cpp
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
-
从头开始遍历,找到
next指向pos的那个节点 -
循环条件:
prev->next != pos,当prev的下一个节点不是pos时继续 -
循环结束时,
prev就是pos的前一个节点
删除节点
cpp
prev->next = pos->next; // 前一个节点指向pos的下一个节点(绕过pos)
free(pos); // 释放pos节点的内存
pos = NULL; // 将局部指针置NULL(好习惯,但非必须)
删除过程图解
情况1:删除非头节点
假设链表 1->2->3->4->NULL,要删除节点 3:
初始状态:
1\|●\]──→\[2\|●\]──→\[3\|●\]──→\[4\|NULL
↑
pos
第一步:找prev
prev从1开始:
prev=1: 1->next=2 ≠ 3 → prev=2
prev=2: 2->next=3 == pos → 停止
此时 prev 指向节点2
第二步:prev->next = pos->next
1\|●\]──→\[2\|●\]──→\[4\|NULL
│ ↑
└─────┘
prev->next 跳过节点3指向节点4
pos(3) 还在中间,但已被跳过
第三步:free(pos)
释放节点3的内存
最终结果:1->2->4->NULL
情况2:删除头节点
假设链表 1->2->3->4->NULL,要删除节点 1:
if (pos == *pphead) 成立
直接调用 SLTPopFront(pphead)
SLTPopFront 执行:
next = (*pphead)->next; // next指向节点2
free(*pphead); // 释放节点1
*pphead = next; // 头指针指向节点2
最终结果:2->3->4->NULL
情况3:删除尾节点
假设链表 1->2->3->4->NULL,要删除节点 4:
初始状态:
1\|●\]──→\[2\|●\]──→\[3\|●\]──→\[4\|NULL
↑
pos
第一步:找prev
prev从1开始,一直走到节点3(3->next=4 == pos)
第二步:prev->next = pos->next // pos->next = NULL
prev(3) 的 next 指向 NULL
第三步:free(pos) // 释放节点4
最终结果:1->2->3->NULL
4.删除pos之后的节点
全部代码如下:
cpp
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
cpp
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos && pos->next);
SLTNode* del = pos->next;
//此时找到了 pos del del->next
pos->next = del->next;
free(del);
del = NULL;
}
cpp
////删除pos之后的节点
SLTNode* find = SLTFind(plist, 2);
SLTEraseAfter(find);
SLTPrint(plist);
输出结果如下:

解析:
函数实现
cpp
void SLTEraseAfter(SLTNode* pos)
{
assert(pos && pos->next); // 确保pos存在且后面有节点
SLTNode* del = pos->next; // del 指向要删除的节点(pos的下一个)
// 此时找到了 pos del del->next
pos->next = del->next; // pos 跳过 del,指向 del 的下一个
free(del); // 释放 del 节点的内存
del = NULL; // 将局部指针置NULL(好习惯)
}
断言检查
cpp
assert(pos && pos->next);
-
同时检查两件事:
-
pos != NULL:位置节点不能为空 -
pos->next != NULL:pos后面必须有节点才能删除
-
-
如果
pos是最后一个节点(pos->next == NULL),断言失败,程序终止
定位要删除的节点
cpp
SLTNode* del = pos->next; // del 指向要删除的节点(pos的下一个)
-
用
del指针保存要删除的节点地址 -
为什么要保存?因为马上要修改
pos->next,如果不保存就找不到这个节点了
重新链接
cpp
pos->next = del->next; // pos 跳过 del,指向 del 的下一个
-
让
pos的next指针直接指向del的下一个节点 -
这样
del节点就从链表中被"跳过"了
释放内存
cpp
free(del); // 释放 del 节点的内存
del = NULL; // 将局部指针置NULL(好习惯)
-
free(del):释放被删除节点的内存 -
del = NULL:局部变量置 NULL(防止误用,但函数结束就销毁,不是必须)
删除过程图解
情况1:删除中间节点
假设链表 1->2->3->4->NULL,要删除节点 2 之后的节点(即删除节点3):
初始状态:
1\|●\]──→\[2\|●\]──→\[3\|●\]──→\[4\|NULL
↑
pos
第一步:SLTNode* del = pos->next;
del 指向节点3
1\|●\]──→\[2\|●\]──→\[3\|●\]──→\[4\|NULL
↑ ↑
pos del
第二步:pos->next = del->next;
del->next 指向节点4
pos->next 从指向 del 改为指向节点4
1\|●\]──→\[2\|●\]──→\[4\|NULL
└─────┘
↑ ↑
pos del(仍指向3,但已被跳过)
第三步:free(del);
释放节点3的内存
最终结果:1->2->4->NULL
情况2:删除最后一个节点
假设链表 1->2->3->4->NULL,要删除节点 3 之后的节点(即删除节点4):
初始状态:
1\|●\]──→\[2\|●\]──→\[3\|●\]──→\[4\|NULL
↑
pos
第一步:del = pos->next; // del指向节点4
第二步:pos->next = del->next; // del->next = NULL
pos->next 指向 NULL
1\|●\]──→\[2\|●\]──→\[3\|NULL
第三步:free(del); // 释放节点4
最终结果:1->2->3->NULL
情况3:错误使用(会导致断言失败)
// 错误1:pos 是最后一个节点
SLTNode* find = SLTFind(plist, 4);
SLTEraseAfter(find); // assert(pos->next) 失败,程序终止
// 错误2:pos 是 NULL
SLTEraseAfter(NULL); // assert(pos) 失败,程序终止
5.销毁链表
全部代码如下:
cpp
//销毁链表
void SListDesTroy(SLTNode** pphead);
cpp
//销毁链表
void SListDesTroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
while(pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
//此时pcur为空
*pphead = NULL;
}
cpp
//销毁链表
SListDesTroy(&plist);
SLTPrint(plist);
输出结果如下:

解析:
初始化遍历指针
cpp
SLTNode* pcur = *pphead; // pcur 指向当前要处理的节点
pcur指向链表的第一个节点
循环释放所有节点
cpp
while (pcur) // 只要 pcur 不为 NULL,继续循环
{
SLTNode* next = pcur->next; // 先保存下一个节点的地址
free(pcur); // 释放当前节点
pcur = next; // 移动到下一个节点
}
为什么需要先保存 next?
因为 free(pcur) 之后,pcur 指向的内存被释放,不能再访问 pcur->next。所以必须在释放之前把下一个节点的地址保存下来。
循环执行过程 (链表 1->2->3->4->NULL):
| 循环次数 | pcur指向 | next保存 | 操作 | 下一轮pcur |
|---|---|---|---|---|
| 第1次 | 节点1 | 节点2 | free(节点1) | 节点2 |
| 第2次 | 节点2 | 节点3 | free(节点2) | 节点3 |
| 第3次 | 节点3 | 节点4 | free(节点3) | 节点4 |
| 第4次 | 节点4 | NULL | free(节点4) | NULL |
| 第5次 | NULL | - | 循环结束 | - |
头指针置 NULL
cpp
*pphead = NULL; // 头指针置 NULL,防止野指针
-
所有节点释放完后,将外部的头指针设为 NULL
-
这样调用者再使用
plist时,知道它是空链表,不会误访问已释放的内存
销毁过程图解
假设链表:1->2->3->NULL
初始状态:
plist → [1|●]──→[2|●]──→[3|NULL]
↑
pcur
第1次循环:
next = pcur->next; // next指向节点2
free(pcur); // 释放节点1
pcur = next; // pcur指向节点2
状态:
plist → (已释放) [2|●]──→[3|NULL]
↑
pcur
第2次循环:
next = pcur->next; // next指向节点3
free(pcur); // 释放节点2
pcur = next; // pcur指向节点3
状态:
plist → (已释放) (已释放) [3|NULL]
↑
pcur
第3次循环:
next = pcur->next; // next = NULL
free(pcur); // 释放节点3
pcur = next; // pcur = NULL
状态:
plist → (已释放) (已释放) (已释放)
循环结束,pcur = NULL
最后:*pphead = NULL;
plist → NULL
以上就是关于单链表的全部内容了!!!!