news 2026/7/23 12:35:56

C 语言工业级通用组件手写 14:单向链表

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C 语言工业级通用组件手写 14:单向链表

目录

前言:

一、单向链表核心本质与应用场景

1. 什么是单向链表

2. 解决的核心痛点

3. 典型工业落地场景

二、核心实现原理

1. 带头结点设计

2. 单向遍历机制

3. 静态节点优先

三、工业级设计规范

1. 封装设计

2. 接口设计

3. 鲁棒约束

4. 线程安全

四、完整可复用源码

1.slist.h

2.slist.c

五、实战演示

六、进阶优化方向

七、面试考点与易错坑点

1.面试问答

2.常见坑

总结


前言:

嵌入式很多资源紧张的 8 位单片机,RAM 极小,不需要反向遍历场景,双向链表prev指针会额外占用内存。单向链表结构最简、内存开销最小,适合简易节点管理。

很多新手分不清单向链表与双向链表适用场景,盲目全部使用双向链表;本篇实现极简工业级单向链表,支持静态节点、无动态内存强制依赖,接口精简,适合多路设备、简易任务列表、日志节点等轻量级管理场景。


一、单向链表核心本质与应用场景

1. 什么是单向链表

单向链表每个节点仅包含后继 next 指针,只能够从头节点向尾部单向遍历;不存在前驱指针,内存占用相比双向链表节省一半指针空间。

特性:节点无需连续内存;支持动态增删;不支持反向遍历;查找指定节点删除时,需要从头遍历

2. 解决的核心痛点

  • 解决小型单片机内存资源紧张问题:省去 prev 指针,减少 RAM 占用。
  • 解决数组长度固定、扩容难:动态挂载节点,不受预设数组大小限制。
  • 解决简易节点管理重复造轮子:多路 IO、简易任务、临时日志统一管理。
  • 规避频繁 malloc 碎片:支持静态定义节点,全程使用静态内存。

3. 典型工业落地场景

  • 简易多路传感器节点登记管理。
  • 临时日志、告警信息临时挂载链表缓存。
  • 简易任务列表,顺序轮询执行。
  • 串口会话简易登记(不需要反向查找场景)。
  • 参数条目简易遍历管理。

二、核心实现原理

1. 带头结点设计

采用独立头节点,头结点不存储业务数据,统一空链表、首尾节点边界处理逻辑,消除大量 if 分支,嵌入式标准写法。

2. 单向遍历机制

只能由 head 依次顺着 next 向后访问节点;

删除目标节点时,需要保存前驱节点指针。

3. 静态节点优先

组件不强制动态堆分配,节点定义为全局 / 局部静态变量,杜绝内存碎片、分配失败风险。

三、工业级设计规范

1. 封装设计

基础链表节点结构体通用,业务结构体内嵌链表节点,不需要内存拷贝。

2. 接口设计

接口功能说明
slist_init初始化链表头结点
slist_add_head头部插入节点
slist_add_tail尾部插入节点
slist_remove移除指定节点
slist_is_empty判断链表为空
slist_foreach单向遍历所有节点

3. 鲁棒约束

  • 空指针全部校验;禁止同一节点重复挂载;
  • 节点移除后置空 next 指针,避免野指针;
  • 纯 C 无第三方依赖,裸机通用。

4. 线程安全

单线程天然安全;

多线程并发操作链表,外部增加关中断或者互斥锁保护。

四、完整可复用源码

1.slist.h

#ifndef SLIST_H #define SLIST_H #include <stddef.h> #include <stdbool.h> #ifdef __cplusplus extern "C" { #endif //单向链表基础节点 typedef struct slist_node { struct slist_node *next; } slist_node_t; /** * @brief 初始化单向链表头 */ void slist_init(slist_node_t *head); /** * @brief 头部插入节点 */ void slist_add_head(slist_node_t *head, slist_node_t *node); /** * @brief 尾部插入节点 */ void slist_add_tail(slist_node_t *head, slist_node_t *node); /** * @brief 删除指定节点 */ bool slist_remove(slist_node_t *head, slist_node_t *node); /** * @brief 判断链表是否为空 */ bool slist_is_empty(slist_node_t *head); // 通过链表节点获取宿主结构体 #define slist_container_of(ptr, type, member) \ ((type *)((char *)(ptr) - offsetof(type, member))) //单向遍历宏 #define slist_foreach(pos, head) \ for (pos = (head)->next; pos != NULL; pos = pos->next) #ifdef __cplusplus } #endif #endif

2.slist.c

#include "slist.h" void slist_init(slist_node_t *head) { if(head == NULL) return; head->next = NULL; } void slist_add_head(slist_node_t *head, slist_node_t *node) { if(head == NULL || node == NULL) return; node->next = head->next; head->next = node; } void slist_add_tail(slist_node_t *head, slist_node_t *node) { if(head == NULL || node == NULL) return; slist_node_t *p = head; while(p->next != NULL) { p = p->next; } node->next = NULL; p->next = node; } bool slist_remove(slist_node_t *head, slist_node_t *node) { if(head == NULL || node == NULL || slist_is_empty(head)) return false; slist_node_t *prev = head; slist_node_t *curr = head->next; while(curr != NULL) { if(curr == node) { prev->next = curr->next; node->next = NULL; return true; } prev = curr; curr = curr->next; } return false; } bool slist_is_empty(slist_node_t *head) { if(head == NULL) return true; return head->next == NULL; }

五、实战演示

#include <stdio.h> #include "slist.h" //业务节点示例 typedef struct { uint8_t dev_id; slist_node_t node; } dev_item_t; dev_item_t dev1, dev2, dev3; int main(void) { slist_node_t slist_head; slist_init(&slist_head); dev1.dev_id = 1; dev2.dev_id = 2; dev3.dev_id = 3; slist_add_tail(&slist_head, &dev1.node); slist_add_tail(&slist_head, &dev2.node); slist_add_tail(&slist_head, &dev3.node); slist_node_t *pos; slist_foreach(pos, &slist_head) { dev_item_t *item = slist_container_of(pos, dev_item_t, node); printf("设备ID:%d\n", item->dev_id); } slist_remove(&slist_head, &dev2.node); printf("删除设备2完成\n"); return 0; }

六、进阶优化方向

  • 增加链表节点计数,不需要遍历即可获取节点总数
  • 缓存尾指针,规避尾插每次从头遍历,提升尾部插入效率
  • 支持按条件查找节点封装通用接口

七、面试考点与易错坑点

1.面试问答

Q1:单向链表与双向链表怎么选型?

答:只需要正向遍历、追求最小内存占用、无频繁随机删除场景 → 单向链表;需要快速删除、双向遍历、频繁随机移除节点 → 双向链表。

Q2:单向链表删除节点为什么需要前驱指针?

答:节点本身无法访问上一级节点,必须遍历保存前驱,修改前驱 next 指针。

Q3:单向链表尾部插入效率短板如何优化?

答:可以额外保存尾指针,不需要每次遍历到链表末尾。

2.常见坑

  • 节点移除不置空 next 指针,引发野指针;
  • 重复添加同一个节点,形成环形链表死循环;
  • 遍历时直接删除当前遍历节点,导致遍历断链崩溃。

总结

  • 单向链表是资源受限单片机首选动态容器,结构极简、内存开销最低。
  • 在不需要反向遍历的场景下,相比双向链表拥有天然 RAM 优势。
  • 适合简易设备管理、任务列表等轻量级业务,是嵌入式底层基础数据结构。

创作不易,如果对你有帮助,欢迎点赞、收藏、转发。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/23 12:35:41

Ubuntu 22.04 LTS安装指南(附详细解决踩坑步骤)

摘要&#xff1a;本文为国内用户提供 Ubuntu 24.04 LTS 系统镜像的完整下载、校验与安装指南。针对官网下载慢、镜像易损坏等常见问题&#xff0c;重点介绍了清华大学、阿里云、中国科学技术大学三大国内镜像站的高速下载方案&#xff0c;详细讲解 Windows 系统下使用 PowerShe…

作者头像 李华
网站建设 2026/7/23 12:35:36

Spring速成笔记:Java初学者进阶必刷!

金三银四已过&#xff0c;不知道大家面试的时候有没有被问到过Spring相关问题&#xff08;循环依赖、事务、生命周期、传播特性、IOC、AOP、设计模式、源码&#xff09;&#xff1f;从之前的博客反馈来看&#xff0c;很多小伙伴其实对Spring框架还没有一个清楚的认知。拿Spring…

作者头像 李华
网站建设 2026/7/23 12:32:25

PyTorch中的autocast与GradScaler协作机制:混合精度训练的底层实现分析

PyTorch中的autocast与GradScaler协作机制&#xff1a;混合精度训练的底层实现分析混合精度训练已成为深度学习训练加速的标准手段&#xff0c;PyTorch通过torch.cuda.amp.autocast和GradScaler两个核心组件提供了开箱即用的支持。本文深入分析两者的协作机制&#xff1a;autoc…

作者头像 李华
网站建设 2026/7/23 12:31:17

Claude Code Skills开发实践与效能提升指南

1. Claude Code Skills最佳实践概述 Claude Code作为当前最先进的AI编程助手之一&#xff0c;其Skills系统提供了强大的扩展能力。Skills本质上是一组可复用的知识模块和工具集&#xff0c;能够显著提升Claude在特定领域的表现。根据Anthropic内部数百个活跃Skills的使用经验&a…

作者头像 李华
网站建设 2026/7/23 12:31:16

TUSB系列8052芯片无JTAG调试:串口打印与Keil ISD51实战指南

1. 项目概述在嵌入式开发领域&#xff0c;尤其是围绕德州仪器&#xff08;TI&#xff09;TUSB2136、TUSB3210、TUSB3410和TUSB5052这类基于8052内核的USB设备控制器进行固件开发时&#xff0c;一个绕不开的难题就是调试。这些芯片以其灵活性和成熟的8052生态而备受青睐&#xf…

作者头像 李华
网站建设 2026/7/23 12:28:42

医药AIGC实战:AI疾病筛查技术解析与应用

1. 医药AIGC实战指南&#xff1a;AI疾病筛查如何重塑药企患者管理去年参与某跨国药企的数字化升级项目时&#xff0c;我亲眼见证了传统患者招募方式的困境&#xff1a;一个针对罕见病的临床试验&#xff0c;花费6个月时间仅招募到目标患者数的30%。而引入AI疾病筛查系统后&…

作者头像 李华