首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >C语言单链表

C语言单链表

作者头像
用户12004530
发布2026-01-23 14:43:56
发布2026-01-23 14:43:56
1000
举报

概念

单链表是 C 语言中一种线性、非连续存储的数据结构,它不像数组那样占用一块连续的内存空间,而是由一个个独立的「节点」通过指针串联而成,且只能从前往后单向遍历

单链表的结构

数据域:存储实际的业务数据(比如 int、char、结构体等);

指针域:存储一个指向「下一个节点」的指针(类型与节点结构体一致)

注意:

  1. 从上图可看出,链式结构在逻辑上是连续的,但是在物理上不一定连续
  2. 现实中的结点一般都是从堆上申请出来的
  3. 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
代码语言:javascript
复制
 struct SListNode
{
	type data;
	struct SListNode* next;
};

一个是我们要存储的数据,一个则是指向下一个节点的指针,我们再稍微对这个结构体改进一下

代码语言:javascript
复制
typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

第一行的typedef的作用就是给int换个名字叫做SLDataType,第二行的typedef的作用就是给struct SListNode这个结构体起一个新的名字叫做SLTNode,后续就可以用SLTNode表示这个结构体

在讲解顺序表的时候说了要动用模块化函数编程的思想,这里也一样

初始化

既然有了结构,我们就可以先初始化单链表的节点,首先链表由一个一个的节点组成,并且每一个节点都指向下一个节点,而最后一个节点就指向NULL,那我们就可以先开辟一块空间把节点存进去,是一块一块的开辟,所以不需要扩展空间

代码语言:javascript
复制
/链表由一个一个的节点组成
	//创建几个节点
	SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
	node1->data = 1;
	SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
	node2->data = 2;
	SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
	node3->data = 3;
	SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
	node4->data = 4;

现在这里面就是先把数据给初始化好,再将节点连接起来

代码语言:javascript
复制
//将四个节点连接起来
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;

现在就可以写一个打印输出的函数看一下效果

代码语言:javascript
复制
void SLTPrint(SLTNode* phead);
代码语言:javascript
复制
void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur != NULL)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

SLTNode* plist = node1;
SLTPrint(plist);

这里定义一个plist的头指针,再根据这个头指针创建一个临时头指针pcur,循环打印

现在我们就把这个单链表初始化好了,现在来实现增删改查的功能

插入

尾插

代码语言:javascript
复制
//尾插
void SLTPushBack(SLTNode* pphead, SLTDataType x);

在尾插之前,我们不管是不是尾插还是头插都会涉及到开辟空间,每一次插入都开辟空间就有点浪费时间和空间了,所以我们可以写一个函数专门用来开辟空间

代码语言:javascript
复制
//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
代码语言:javascript
复制
//尾插
void SLTPushBack(SLTNode* pphead, SLTDataType x)
{
	assert(pphead);
	//空链表和非空链表

	SLTNode* newnode = SLTBuyNode(x);
	if (pphead == NULL)
	{
		pphead = newnode;
	}
	else
	{
		//找尾
		SLTNode* ptail = pphead;
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		//ptail指向尾节点
		ptail->next = newnode;
	}
}
代码语言:javascript
复制
 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(plist, 1);
}
int main()
{
    SListtTest02();
}

现在我们来调试一下

初始情况下置为NULL

但当我的代码走完尾插的模块的时候还是没有数据

但我已经传递数据过去为什么还是NULL?继续调试

此时代码已经调试到了走到尾插模块最后一行,现在跳出这个模块

此时pphead变了,plist没有变,形参发生了改变,而实参没有,说明这里我们传递的是值,而不是地址,所以我们这里需要传指针地址,就需要用二级指针接收,正确的代码是这样的

代码语言:javascript
复制
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
代码语言:javascript
复制
//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	//空链表和非空链表

	SLTNode* newnode = SLTBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找尾
		SLTNode* ptail = pphead;
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		//ptail指向尾节点
		ptail->next = newnode;
	}
}

再调用一下打印函数

头插

代码语言:javascript
复制
void SLTPushFront(SLTNode** pphead, SLTDataType x);
代码语言:javascript
复制
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

单链表的头插不需要把所有节点都往后移,只需要将头节点和头插的节点连接就可以了

代码语言:javascript
复制
 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(&plist, 1);
    SLTPushBack(&plist, 2);
    SLTPushBack(&plist, 3);
    SLTPushBack(&plist, 4);
    SLTPushFront(&plist, 5);
    SLTPrint(plist);
}
int main()
{
    SListtTest02();
}

删除

尾删

代码语言:javascript
复制
void STLPopBack(SLTNode** pphead);
代码语言:javascript
复制
//尾删
void STLPopBack(SLTNode** pphead)
{
	assert(pphead);
	//链表不能为空
	assert(*pphead);
	//链表只有一个节点和多个节点
	if ((*pphead)->next == NULL)//->优先级高于*
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* prev = *pphead;
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
        prev->next=ptail;
	}

}
代码语言:javascript
复制
 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(&plist, 1);
    SLTPushBack(&plist, 2);
    SLTPushBack(&plist, 3);
    SLTPushBack(&plist, 4);
    SLTPushFront(&plist, 5);
    STLPopBack(&plist);
    SLTPrint(plist);
    
}
int main()
{
    SListtTest02();
}

头删

代码语言:javascript
复制
void  STLPopFront(SLTNode** pphead);
代码语言:javascript
复制
//头删
void  STLPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* news = (*pphead)->next;
	free(*pphead);
	*pphead = news;
}
代码语言:javascript
复制
 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(&plist, 1);
    SLTPushBack(&plist, 2);
    SLTPushBack(&plist, 3);
    SLTPushBack(&plist, 4);
    SLTPushFront(&plist, 5);
    STLPopBack(&plist);
    SLTPrint(plist);
    STLPopFront(&plist);
    SLTPrint(plist);
    
}
int main()
{
    SListtTest02();
}

查找

代码语言:javascript
复制
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
代码语言:javascript
复制
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* news = phead;
	while (news)
	{
		if (news->data == x)
		{
			return news;
		}
		news = news->next;
	}
	return -1;
}
代码语言:javascript
复制
 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(&plist, 1);
    SLTPushBack(&plist, 2);
    SLTPushBack(&plist, 3);
    SLTPushBack(&plist, 4);
    SLTPushFront(&plist, 5);
    STLPopBack(&plist);
    SLTPrint(plist);
    STLPopFront(&plist);
    SLTPrint(plist);
    SLTNode * news=SLTFind(plist, 2);
if (news == NULL)
{
	printf("没找到\n");
}
else
{
	printf("找到了\n");
}
}
int main()
{
    SListtTest02();
}

在指定位置之前插入

代码语言:javascript
复制
void  STLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);

三个参数,一个是传递的地址,一个是指定的位置,一个是要插入的在值,这里我就以上面查找的值的位置来插入

代码语言:javascript
复制
//在指定位置之前插入
void  STLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	SLTNode* newnode = SLTBuyNode(x);
	assert(pphead && *pphead);
	assert(pos);
	//若pos==*pphead 说明是头插
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		newnode->next = pos;
		prev->next = newnode;
	}

}
代码语言:javascript
复制
 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(&plist, 1);
    SLTPushBack(&plist, 2);
    SLTPushBack(&plist, 3);
    SLTPushBack(&plist, 4);
    SLTPushFront(&plist, 5);
    STLPopBack(&plist);
    SLTPrint(plist);
    STLPopFront(&plist);
    SLTPrint(plist);
    SLTNode * news=SLTFind(plist, 2);
    STLInsert(&plist,news,12);
    SLTPrint(plist);
}
int main()
{
    SListtTest02();
}

在指定位置之后插入

代码语言:javascript
复制
void STLInsertAfter(SLTNode* pos, SLTDataType x);
代码语言:javascript
复制
//在指定位置之后插入
void STLInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
代码语言:javascript
复制
 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(&plist, 1);
    SLTPushBack(&plist, 2);
    SLTPushBack(&plist, 3);
    SLTPushBack(&plist, 4);
    SLTPushFront(&plist, 5);
    STLPopBack(&plist);
    SLTPrint(plist);
    STLPopFront(&plist);
    SLTPrint(plist);
    SLTNode * news=SLTFind(plist, 2);
    STLInsert(&plist,news,12);
    SLTPrint(plist);
    STLInsertAfter(news, 333);
    SLTPrint(plist);
}
int main()
{
    SListtTest02();
}

删除pos节点

代码语言:javascript
复制
void SLTErase(SLTNode** pphead, SLTNode* pos);
代码语言:javascript
复制
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(*pphead && pphead);
	assert(pos);
	//pos是头节点和不是头结点
	if (pos == *pphead)
	{
		STLPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

还是以上面查找到的2来举例

代码语言:javascript
复制
 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(&plist, 1);
    SLTPushBack(&plist, 2);
    SLTPushBack(&plist, 3);
    SLTPushBack(&plist, 4);
    SLTPushFront(&plist, 5);
    STLPopBack(&plist);
    SLTPrint(plist);
    STLPopFront(&plist);
    SLTPrint(plist);
    SLTNode * news=SLTFind(plist, 2);
    STLInsert(&plist,news,12);
    SLTPrint(plist);
    STLInsertAfter(news, 333);
    SLTPrint(plist);
    SLTErase(&plist,news);
    SLTPrint(plist);
}
int main()
{
    SListtTest02();
}

删除pos之后的节点

代码语言:javascript
复制
void SLTEraseAfter(SLTNode* pos);
代码语言:javascript
复制
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);
	SLTNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}
代码语言:javascript
复制
 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(&plist, 1);
    SLTPushBack(&plist, 2);
    SLTPushBack(&plist, 3);
    SLTPushBack(&plist, 4);
    SLTPushFront(&plist, 5);
    STLPopBack(&plist);
    SLTPrint(plist);
    STLPopFront(&plist);
    SLTPrint(plist);
    SLTNode * news=SLTFind(plist, 2);
    STLInsert(&plist,news,12);
    SLTPrint(plist);
    STLInsertAfter(news, 333);
    SLTPrint(plist);
    /*SLTErase(&plist,news);
    SLTPrint(plist);*/
    SLTEraseAfter(news);
    SLTPrint(plist);
}
int main()
{
    SListtTest02();
}

因为上一个模块将2删除了,所以就把上一个模块注释掉

销毁链表

代码语言:javascript
复制
void SListDesTroy(SLTNode** pphead);
代码语言:javascript
复制
//销毁链表
void SListDesTroy(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}
代码语言:javascript
复制
 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(&plist, 1);
    SLTPushBack(&plist, 2);
    SLTPushBack(&plist, 3);
    SLTPushBack(&plist, 4);
    SLTPushFront(&plist, 5);
    STLPopBack(&plist);
    SLTPrint(plist);
    STLPopFront(&plist);
    SLTPrint(plist);
    SLTNode * news=SLTFind(plist, 2);
    STLInsert(&plist,news,12);
    SLTPrint(plist);
    STLInsertAfter(news, 333);
    SLTPrint(plist);
    /*SLTErase(&plist,news);
    SLTPrint(plist);*/
    SLTEraseAfter(news);
    SLTPrint(plist);
    SListDesTroy(&plist);
    SLTPrint(plist);
}
int main()
{
    SListtTest02();
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-01-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概念
  • 单链表的结构
  • 初始化
  • 插入
    • 尾插
    • 头插
  • 删除
    • 尾删
    • 头删
  • 查找
  • 在指定位置之前插入
  • 在指定位置之后插入
  • 删除pos节点
  • 删除pos之后的节点
  • 销毁链表
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档