本次编译环境为VS2022
文章目录
前言
在上一次的博客中我们实现了基于顺序表的通讯录项目 于是紧跟着数据结构的大纲 链表也是迫不及待的来了
所谓链表就是 以 链条形式联合在一起的表格 它相对于顺序表的结构体内容更少但理解难度不小 那让我们来认识一下并学习如何用c语言来实现单链表
单链表代码的实现过程
一.创建文件 明晰架构
1.创建“老三样”

仍然是我们的 头文件 :用来定义函数 和结构体内容的实现
两个源文件 .c文件负责 实现函数 test.c则为我们的测试函数
2.结构体的创建和其基本原理
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int CLDateType;
typedef struct ChainList
{
CLDateType date;
CLNode * next;
}CLNode;
我们逐条分析一下 前四行是我们要用到的头文件 为vs编译器自己的库
后面我们用typedef来将int 类型重新命名 其主要任务是储存我们要加入的整数类型和我们节点里放置的 整形
再看到 后面的结构体内容
我们将其重新命名了一下后 里面有一个数据date其主要任务就是储存节点的整数部分 后面的 next其实就是我们节点里指向下一个节点的关键指针 其类型为我们的结构体指针类型
我们要实现的单链表大致为下图

二.单链表的函数实现
1.打印函数的实现
void CLPrint(CLNode* phead)
{
CLNode* pcur = phead;
while (pcur->next)
{
printf("%d->", pcur->date);
pcur = pcur->next;
}
printf("NULL\n");
}
这里给到打印函数的实现
我们可以看到 在打印函数里面我们定义了一个结构体指针变量来充当我们结构体的第一个节点的地址
为了让其不断的通过next->延伸到最后一个节点也就是 next->指向为null时 我们用了while循环 然后不断的打印里面的数据最后到了NULL时退出循环
2.头插和尾插的实现
为节点的插入开辟空间
因为我们一开始还是没有空间的 所以我们要通过使用动态内存管理的内容来解决 因为是开辟而不是扩容 所以我们用到 malloc来实现
CLNode* ApplyNode(CLDateType x)
{
//这里我们创建新节点 用的就是结构体指针 并且开辟空间大小也因该为结构体指针大小
CLNode* newnode = (CLNode*)malloc(sizeof(CLNode*));
if (newnode == NULL)//判断newnode是否为空
{
perror("malloc fail");
return NULL;
}
//完成后
newnode->date = x;
newnode->next = NULL;
return newnode;
}
可以看到 我们这个函数其返回类型为clnode*原因是在头插尾插代码中 接收的是 节点的类型为结构体指针类型
剩下的点都较为简单 不过多赘述
一.尾插的实现
void CLPushBack(CLNode** pphead, CLDateType x)
//这里为什么要传二级指针 我是这么理解的 因为节点本身 就是CLNode* 类型相当于一级指针
//如果要接受 并且使用 作为形参因该是用二级指针来接受
//**pphead ->第一个节点 *pphead->指向第一个节点的指针 pphead->第一个节点的指针的地址
//要是想更简单理解就把第一个去掉:
{
assert(*pphead && pphead);//
CLNode* newnode = ApplyNode(x);
//到这里我们就创建了一个新节点 此刻想着如何插入
if (*pphead ==NULL)//我们一开始没节点的时候的情况
{
newnode = *pphead;
}
else
{
CLNode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
ptail->next = newnode;
newnode->next = NULL;
}
}
对于尾插的代码 其实主要难以理解的点 就是 为什么要传二级指针??
对于此我的理解是 因为结构体变量->next其实就是结构体指针 致使我们的节点也一样 所以我们在函数接收的时候用二级指针来接收一级指针变量
pphead 其实就是地址 *pphead就是指向节点的地址 **pphead就是节点
所以我们在测试代码过程中 有二级指针的地方不会忘记要加&
当然在此过程中不要忘记判断 pphead是否为空

也可以通过看图理解
二.头插的实现
void CLPustFront(CLNode** pphead, CLDateType x)
{
assert(*pphead && pphead);
CLNode* newnode = ApplyNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
头插相对简单 就是将其的next指向头节点后让自己成为头节点即可

3.头删尾删的实现
一.尾删的实现
void CLDelBack(CLNode** pphead)
{
assert(*pphead && pphead);
CLNode* ptail = *pphead;
CLNode* pre = *pphead;
if ((*pphead)->next = NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
while (ptail->next)
{
ptail = ptail->next;
pre->next = ptail;
}
free(ptail);
ptail = NULL;
pre->next = NULL;
}
}
这是我们尾删的代码为了方便理解我们从图中来看 给到图

我们通过看 这个图可以看到我们定义的两个结构体指针变量
ptail pre
其中ptail是我们用来找尾的 找到后进行free删除
而pre 则是 我们用来找node3 的最后将其的next赋予NULL则这个链表完美实现
当然不要忘了链表中只有一个节点的情况 我们要删除的话也是free掉
二.头删的实现
void CLDelFront(CLNode** pphead)
{
assert(*pphead && pphead);
CLNode* del = (*pphead)->next;
free(*pphead);
*pphead = del;
}
头删的代码相对简单而且特殊情况直接被现有代码包含了
我们这里还是为了保证代码运行的可行性 来创建临时变量来接收node2
4.在指定位置之前插入 之后插入的实现
一.在指定位置之前插入

这是我们的思路图 其中逻辑较为简单 就不讲了!
转化为代码
void CLInsertBack(CLNode** pphead, CLNode* pos, CLDateType x)
{
assert(*pphead && pphead);
assert(pos);
CLNode* newnode = ApplyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
二.在指定位置之后插入

我们这里看到 我们给定了一个 pre让它成为pos前面的node2 从而让我们 node2->next = newnode有了着落
后面便逻辑简单了
给到我们的代码
void CLInsertFront(CLNode** pphead, CLNode* pos, CLDateType x)
{
assert(pphead && *pphead);
assert(pos);
CLNode* newnode = ApplyNode(x);
if (pos == *pphead)
{
CLPustFront(pphead, x);
}
else
{
CLNode* pre = *pphead;
while (pre->next != pos)
{
pre = pre->next;
}
//此时pre跳出循环它在pos位置之前
pre->next = newnode;
newnode->next = pos;
}
}
5.在指定位置删除
一.在指定位置之后删除
void CLDelPosBack(CLNode* pos)
{
assert(pos&&pos->next);//在这里要保证pos->next后面不为空才能删
CLNode* del = pos->next;
pos->next = del->next;
free(del);
del = NULL;
}
这里看到我们没有传二级指针 我们只进行节点的删除操作 只需要确定pos的位置即可 所以不需要传链表进来
我们这里第一步就是要确定pos后面不为空 若为空 是删不了的
后面我们通过用del指针来充当pos的下一位 从而让pos->next成功指向pos的后两位最后free(del)
二.在指定位置删除
void CLDelPos(CLNode** pphead, CLNode* pos)
{
assert(*pphead && pphead);
assert(pos);
if (pos == *pphead)
{
//调用头删
CLDelFront(pphead);
}
else
{
CLNode* pre = *pphead;
while (pre->next != pos)
{
pre = pre->next;
}
pre->next = pos->next;
free(pos);
pos = NULL;
}
}
在指定位置删除并无太难的点 大部分都是之前的代码集合
6. 销毁函数的实现
函数实现部分
void CLDestory(CLNode** pphead)
{
assert(*pphead && pphead);
CLNode* del = *pphead;
while (del)
{
CLNode* next = del->next;
free(del);
del = next;
}
*pphead = NULL;
}
销毁代码的实现是通过新建了两个变量通过循环销毁赋值的过程来不断实现代码
最后给*pphead赋值为空
总结
以上便是单链表的实现代码 测试代码大家可以在测试文件中慢慢测试 同各国调试来提升自己的代码水平!!!
后面把所有代码给到各位
.h文件
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int CLDateType;
typedef struct ChainList
{
CLDateType date;
CLNode * next;
}CLNode;
//打印函数
void CLPrint(CLNode* phead);
//链表的头插尾插
//尾插
void CLPushBack(CLNode** pphead, CLDateType x);
//头插
void CLPustFront(CLNode** pphead, CLDateType x);
//尾删
void CLDelBack(CLNode** pphead);
//头删
void CLDelFront(CLNode** pphead);
//在指定位置之后插入
void CLInsertBack(CLNode** pphead, CLNode* pos, CLDateType x);
//在指定位置之前插入
void CLInsertFront(CLNode** pphead, CLNode* pos, CLDateType x);
//在指定位置之后删除
void CLDelPosBack(CLNode* pos);
//在指定位置删除
void CLDelPos(CLNode** pphead, CLNode* pos);
//销毁函数
void CLDestory(CLNode** pphead);
.c文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "CL.h"
void CLPrint(CLNode* phead)
{
CLNode* pcur = phead;
while (pcur->next)
{
printf("%d->", pcur->date);
pcur = pcur->next;
}
printf("NULL\n");
}
CLNode* ApplyNode(CLDateType x)
{
//这里我们创建新节点 用的就是结构体指针 并且开辟空间大小也因该为结构体指针大小
CLNode* newnode = (CLNode*)malloc(sizeof(CLNode*));
if (newnode == NULL)//判断newnode是否为空
{
perror("malloc fail");
return NULL;
}
//完成后
newnode->date = x;
newnode->next = NULL;
return newnode;
}
void CLPushBack(CLNode** pphead, CLDateType x)
//这里为什么要传二级指针 我是这么理解的 因为节点本身 就是CLNode* 类型相当于一级指针
//如果要接受 并且使用 作为形参因该是用二级指针来接受
//**pphead ->第一个节点 *pphead->指向第一个节点的指针 pphead->第一个节点的指针的地址
//要是想更简单理解就把第一个去掉:
{
assert(*pphead && pphead);//
CLNode* newnode = ApplyNode(x);
//到这里我们就创建了一个新节点 此刻想着如何插入
if (*pphead ==NULL)//我们一开始没节点的时候的情况
{
newnode = *pphead;
}
else
{
CLNode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
ptail->next = newnode;
newnode->next = NULL;
}
}
void CLPustFront(CLNode** pphead, CLDateType x)
{
assert(*pphead && pphead);
CLNode* newnode = ApplyNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
void CLDelBack(CLNode** pphead)
{
assert(*pphead && pphead);
CLNode* ptail = *pphead;
CLNode* pre = *pphead;
if ((*pphead)->next = NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
while (ptail->next)
{
ptail = ptail->next;
pre->next = ptail;
}
free(ptail);
ptail = NULL;
pre->next = NULL;
}
}
void CLDelFront(CLNode** pphead)
{
assert(*pphead && pphead);
CLNode* del = (*pphead)->next;
free(*pphead);
*pphead = del;
}
void CLInsertBack(CLNode** pphead, CLNode* pos, CLDateType x)
{
assert(*pphead && pphead);
assert(pos);
CLNode* newnode = ApplyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
void CLInsertFront(CLNode** pphead, CLNode* pos, CLDateType x)
{
assert(pphead && *pphead);
assert(pos);
CLNode* newnode = ApplyNode(x);
if (pos == *pphead)
{
CLPustFront(pphead, x);
}
else
{
CLNode* pre = *pphead;
while (pre->next != pos)
{
pre = pre->next;
}
//此时pre跳出循环它在pos位置之前
pre->next = newnode;
newnode->next = pos;
}
}
void CLDelPosBack(CLNode* pos)
{
assert(pos&&pos->next);//在这里要保证pos->next后面不为空才能删
CLNode* del = pos->next;
pos->next = del->next;
free(del);
del = NULL;
}
void CLDelPos(CLNode** pphead, CLNode* pos)
{
assert(*pphead && pphead);
assert(pos);
if (pos == *pphead)
{
//调用头删
CLDelFront(pphead);
}
else
{
CLNode* pre = *pphead;
while (pre->next != pos)
{
pre = pre->next;
}
pre->next = pos->next;
free(pos);
pos = NULL;
}
}
void CLDestory(CLNode** pphead)
{
assert(*pphead && pphead);
CLNode* del = *pphead;
while (del)
{
CLNode* next = del->next;
free(del);
del = next;
}
*pphead = NULL;
}
感谢各位阅读 彦祖亦菲不要吝啬自己的点赞哦!!!
1万+

被折叠的 条评论
为什么被折叠?



