C语言实现单链表

一、什么是单链表

单链表是 C 语言中最基础、最重要的数据结构之一,非常适合用来练习结构体、指针和动态内存管理。

单链表的每个结点由两部分组成:数据域和指针域。数据域用于存储实际数据,指针域用于保存下一个结点的地址,多个结点通过指针依次连接,最终以 NULL 结尾形成完整链表。

二、单链表头文件设计(SList.h)

在实现单链表之前,通常先定义好结点结构和对外提供的操作接口,代码如下。

java 复制代码
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

void SLTPrint(SLTNode* phead);
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);

void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);

SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
void SLTErase(SLTNode** pphead, SLTNode* pos);
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
void SLTEraseAfter(SLTNode* pos);

SLTDataType 使用 typedef 定义为 int,方便后续统一修改链表中存储的数据类型。

SLTNode 结构体表示单链表的结点,其中 data 为数据域,next 为指针域,指向下一个结点。多个结点通过 next 串联形成完整链表。

SLTPrint 用于遍历并打印链表,主要用于调试和测试阶段查看链表结构是否正确。

SLTPushBackSLTPushFront 分别实现尾插和头插操作,由于它们需要修改头指针,因此参数采用二级指针 SLTNode** pphead

SLTPopBackSLTPopFront 用于删除链表尾结点和头结点,同样需要修改头指针,所以也使用二级指针作为参数。

SLTFind 用于在链表中查找指定值,若找到则返回该结点指针,否则返回 NULL,便于后续进行插入或删除等操作。

SLTInsert 用于在指定结点 pos 前插入新结点,如果 pos 为头结点,则等价于头插操作;SLTErase 用于删除指定结点 pos

SLTInsertAfterSLTEraseAfter 分别表示在指定结点之后插入新结点,以及删除指定结点之后的结点。这两个操作不需要修改头指针,因此参数只需使用一级指针。

三、单链表实现(SList.c)

在前文中已经完成了单链表头文件的接口设计,下面给出对应的 .c 文件实现,通过这些基础操作,可以完整实现单链表的增删查改功能。

在实现功能前,我们增加一个函数SLTNode* BuySLTNode(SLTDataType x),来动态申请内存。该函数使用 malloc 申请空间,并对结点数据域和指针域进行初始化。如果内存申请失败,则打印错误信息并返回。完整代码如下。

java 复制代码
#include"SList.h"

SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySLTNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != NULL)
		{
			cur = cur->next;
		}
		cur->next = newnode;
	}
}

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySLTNode(x);
		newnode->next = *pphead;
		*pphead = newnode;
}

void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* cur = *pphead;
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		while (cur->next->next != NULL)
		{
			cur = cur->next;
		}
		free(cur->next);
		cur->next = NULL;
	}
}

void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* cur = *pphead;
	*pphead = cur->next;
	free(cur);
	cur = NULL;
}

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{

	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;

}

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuySLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = pos->next;
		free(pos);
	}
}

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLTNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

四、测试代码(Test.c)

java 复制代码
#include"SList.h"

int main()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);

	SLTPrint(plist);

	SLTPushFront(&plist, 6);
	SLTPushFront(&plist, 7);
	SLTPushFront(&plist, 8);
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPopFront(&plist);
	SLTPrint(plist);

	// 值为2那个节点  *2
	SLTNode* ret = SLTFind(plist, 2);
	ret->data *= 2;
	SLTPrint(plist);

	SLTInsert(&plist, ret, 20);
	SLTPrint(plist);

	/*SLTErase(&plist, ret);
	ret = NULL;
	SLTPrint(plist);*/


	SLTInsertAfter(ret, 666);
	SLTPrint(plist);

	SLTEraseAfter(ret);
	SLTPrint(plist);


}

结果如下。

相关推荐
vivo互联网技术4 小时前
CVPR 2026 | 全新强化学习框架 BeautyGRPO:重塑真实人像
算法·大模型·cvpr·影像
Darling噜啦啦5 小时前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
用户497863050738 小时前
(一)小红的数组操作
算法·编程语言
怕浪猫11 小时前
Electron 系列文章封面图
算法·架构·前端框架
徐小夕13 小时前
JitWord 3.0 正式发布,高精度Word异构解析+复杂组件兼容,打造web端协同Word编辑器
前端·vue.js·算法
LDR0061 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术1 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript
通信小呆呆1 天前
当算法有了“五感”:多模态数据融合如何向人体感官协同学习?
人工智能·学习·算法·机器学习·机器人
码云数智-园园1 天前
C++20 Modules 模块详解
java·开发语言·spring