找回密码
 立即注册
首页 业界区 安全 02-链表

02-链表

思矿戳 2025-9-28 17:55:38
概述

链表作为 C 语言中一种基础的数据结构,在平时写程序的时候用的并不多,但在操作系统里面使用的非常多。理解链表及其在 RTOS 中的应用,这对后续深入学习 RTOS 内核机制非常重要。
一、什么是链表?

链表是一种动态数据结构,由多个「节点」通过指针连接而成。每个节点包含两部分:

  • 数据域:存储实际数据(如整数、结构体等);
  • 指针域:存储下一个(或上一个)节点的地址,用于连接节点。
与数组的核心区别

  • 数组需要预先分配固定大小的连续内存,大小不可动态调整;
  • 链表的节点在内存中可以不连续,通过指针关联,大小可动态增减,插入 / 删除操作更灵活(无需移动大量元素)。
二、C 语言中链表的基础实现

在 C 语言中,链表通过「结构体 + 指针」实现。我们先从最基础的单链表开始讲解。
1. 定义节点结构

单链表的每个节点包含「数据」和「指向下一节点的指针」:
1.png
  1. // 单链表节点结构
  2. typedef struct Node {
  3.     int data;               // 数据域(可替换为任意类型,如结构体)
  4.     struct Node* next;      // 指针域:指向 next 节点
  5. } Node;
复制代码
如果是双向链表(在RTOS 中更常用),还会增加一个「指向前一节点的指针」,方便双向遍历和操作:
2.png
  1. // 双向链表节点结构
  2. typedef struct DNode {
  3.     int data;
  4.     struct DNode* prev;     // 指向 prev 节点
  5.     struct DNode* next;     // 指向 next 节点
  6. } DNode;
复制代码
2. 基础操作(以单链表为例)

链表的核心操作包括:创建节点、初始化链表、插入、删除、遍历、释放内存。
(1)创建节点

通过 malloc 动态分配节点内存,并初始化数据和指针:
  1. // 创建新节点(返回节点指针,失败返回NULL)
  2. Node* createNode(int data) {
  3.     Node* newNode = (Node*)malloc(sizeof(Node));
  4.     if (newNode == NULL) {
  5.         return NULL; // 内存分配失败
  6.     }
  7.     newNode->data = data;
  8.     newNode->next = NULL; // 新节点默认指向NULL
  9.     return newNode;
  10. }
复制代码
(2)初始化链表(头节点)

通常用一个「头节点」作为链表的起点(头节点可不含实际数据,仅用于简化操作):
  1. // 初始化链表(返回头节点)
  2. Node* initList() {
  3.     Node* head = createNode(0); // 头节点数据可忽略
  4.     return head;
  5. }
复制代码
(3)插入节点(尾部插入为例)

在链表末尾添加新节点:
  1. // 向链表尾部插入节点
  2. void insertTail(Node* head, int data) {
  3.     if (head == NULL) return;
  4.    
  5.     Node* newNode = createNode(data);
  6.     if (newNode == NULL) return;
  7.    
  8.     // 遍历到链表尾部
  9.     Node* cur = head;
  10.     while (cur->next != NULL) {
  11.         cur = cur->next;
  12.     }
  13.     // 插入新节点
  14.     cur->next = newNode;
  15. }
复制代码
(4)删除节点(按值删除为例)

找到目标节点并释放其内存,同时调整指针连接:
  1. // 从链表中删除首个值为data的节点
  2. void deleteNode(Node* head, int data) {
  3.     if (head == NULL || head->next == NULL) return;
  4.    
  5.     Node* cur = head->next; // 当前节点
  6.     Node* prev = head;      // 前序节点
  7.    
  8.     while (cur != NULL) {
  9.         if (cur->data == data) {
  10.             prev->next = cur->next; // 跳过当前节点
  11.             free(cur);              // 释放内存
  12.             return;
  13.         }
  14.         prev = cur;
  15.         cur = cur->next;
  16.     }
  17. }
复制代码
(5)遍历链表

从头节点开始,通过指针依次访问每个节点:
  1. // 遍历并打印链表所有数据
  2. void traverseList(Node* head) {
  3.     if (head == NULL || head->next == NULL) {
  4.         printf("链表为空\n");
  5.         return;
  6.     }
  7.    
  8.     Node* cur = head->next;
  9.     while (cur != NULL) {
  10.         printf("%d ", cur->data);
  11.         cur = cur->next;
  12.     }
  13.     printf("\n");
  14. }
复制代码
(6)释放链表

避免内存泄漏,需要手动释放所有节点:
  1. // 释放整个链表
  2. void freeList(Node* head) {
  3.     if (head == NULL) return;
  4.    
  5.     Node* cur = head;
  6.     Node* temp;
  7.     while (cur != NULL) {
  8.         temp = cur;
  9.         cur = cur->next;
  10.         free(temp);
  11.     }
  12. }
复制代码
三、链表在 RTOS 中的核心应用

RTOS的核心是任务管理、资源调度、事件处理等,而链表是实现这些功能的「基础设施」。原因是:

  • RTOS 需要频繁添加 / 删除任务、消息、定时器等对象;
  • 链表的动态性和高效操作(尤其是双向链表的插入 / 删除)能满足实时性要求。
1. 任务管理(最核心的应用)

RTOS 中每个任务由「任务控制块(TCB)」描述(包含任务优先级、状态、栈指针等信息)。这些 TCB 通常通过链表组织,常见场景:

  • 按任务状态分组:就绪态、阻塞态、挂起态的任务分别组成独立链表(如「就绪链表」「阻塞链表」)。
    例如:当一个任务等待信号量时,会从「就绪链表」移到「阻塞链表」;当信号量释放时,再从「阻塞链表」移回「就绪链表」。
  • 按优先级排序:同状态的任务(如就绪态)可能按优先级组成链表,调度器只需从链表头部取最高优先级任务运行。
2. 消息队列

RTOS 中任务间通信常用「消息队列」,消息通常以链表形式存储:

  • 新消息通过「尾部插入」加入队列;
  • 接收消息时从「头部取出」,避免内存拷贝(直接通过指针传递)。
    这种方式比数组实现的队列更灵活(无需预设最大长度)。
3. 定时器管理

RTOS 中的软件定时器(如延时、定时回调)通常用链表管理:

  • 所有定时器组成一个「定时器链表」,按超时时间排序;
  • 系统时钟中断触发时,遍历链表检查是否有定时器超时,若超时则执行回调函数。
4. RTOS 中链表的特殊设计

为满足实时性,RTOS 的链表实现与通用链表有差异:

  • 双向循环链表:几乎所有 RTOS 都用双向循环链表,每个节点有prev和next指针,且尾节点指向头节点,插入 / 删除操作可在 O (1) 时间完成。
  • 静态内存:避免使用malloc/free(可能导致内存碎片和不确定的执行时间),而是预先从内存池分配节点,操作时仅调整指针。
  • 宏定义简化操作:通过宏封装链表操作(如LIST_INSERT_AFTER LIST_REMOVE),减少代码冗余,保证高效性。
四、总结


  • 链表是 C 语言中重要的动态数据结构,通过指针连接节点,适合频繁插入 / 删除的场景;
  • 掌握链表是理解 RTOS 内核的基础 ——RTOS 用链表管理任务、消息、定时器等核心对象,其高效的操作特性是实时性的保障。
如下为链表操作demo例子,方便学习理解:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. // 单向链表节点结构体
  5. typedef struct student {
  6.     // 数据域
  7.     int num;        // 学号
  8.     int score;      // 分数
  9.     char name[20];  // 姓名
  10.     // 指针域
  11.     struct student *next;
  12. } STU;
  13. // 双向链表节点结构体
  14. typedef struct double_student {
  15.     // 数据域
  16.     int num;        // 学号
  17.     int score;      // 分数
  18.     char name[20];  // 姓名
  19.     // 指针域
  20.     struct double_student *front;  // 保存上一个节点的地址
  21.     struct double_student *next;   // 保存下一个节点的地址
  22. } DSTU;
  23. // ==================== 单向链表操作函数 ====================
  24. // 单向链表创建(尾插法)
  25. void link_creat_head(STU **p_head, STU *p_new) {
  26.     STU *p_mov = *p_head;
  27.     if (*p_head == NULL) {  // 当第一次加入链表为空时,head指向p_new
  28.         *p_head = p_new;
  29.         p_new->next = NULL;
  30.     } else {  // 第二次及以后加入链表
  31.         while (p_mov->next != NULL) {
  32.             p_mov = p_mov->next;  // 找到原有链表的最后一个节点
  33.         }
  34.         p_mov->next = p_new;  // 将新申请的节点加入链表
  35.         p_new->next = NULL;
  36.     }
  37. }
  38. // 单向链表遍历
  39. void link_print(STU *head) {
  40.     STU *p_mov;
  41.     p_mov = head;
  42.     printf("\n=== 单向链表遍历结果 ===\n");
  43.     while (p_mov != NULL) {
  44.         printf("学号=%d 分数=%d 姓名:%s\n", p_mov->num, p_mov->score, p_mov->name);
  45.         p_mov = p_mov->next;
  46.     }
  47.     printf("========================\n");
  48. }
  49. // 单向链表释放
  50. void link_free(STU **p_head) {
  51.     STU *pb = *p_head;
  52.     while (*p_head != NULL) {
  53.         pb = *p_head;
  54.         *p_head = (*p_head)->next;
  55.         free(pb);
  56.         pb = NULL;
  57.     }
  58. }
  59. // 按学号查找节点
  60. STU *link_search_num(STU *head, int num) {
  61.     STU *p_mov;
  62.     p_mov = head;
  63.     while (p_mov != NULL) {
  64.         if (p_mov->num == num) {  // 找到了
  65.             return p_mov;
  66.         }
  67.         p_mov = p_mov->next;
  68.     }
  69.     return NULL;  // 没有找到
  70. }
  71. // 按姓名查找节点
  72. STU *link_search_name(STU *head, char *name) {
  73.     STU *p_mov;
  74.     p_mov = head;
  75.     while (p_mov != NULL) {
  76.         if (strcmp(p_mov->name, name) == 0) {  // 找到了
  77.             return p_mov;
  78.         }
  79.         p_mov = p_mov->next;
  80.     }
  81.     return NULL;  // 没有找到
  82. }
  83. // 删除指定学号的节点
  84. void link_delete_num(STU **p_head, int num) {
  85.     STU *pb, *pf;
  86.     pb = pf = *p_head;
  87.     if (*p_head == NULL) {  // 链表为空,不用删
  88.         printf("链表为空,没有您要删的节点\n");
  89.         return;
  90.     }
  91.     while (pb->num != num && pb->next != NULL) {  // 循环找要删除的节点
  92.         pf = pb;
  93.         pb = pb->next;
  94.     }
  95.     if (pb->num == num) {  // 找到了一个节点的num和num相同
  96.         if (pb == *p_head) {  // 要删除的节点是头节点
  97.             *p_head = pb->next;
  98.         } else {
  99.             pf->next = pb->next;
  100.         }
  101.         free(pb);
  102.         pb = NULL;
  103.         printf("删除学号为 %d 的节点成功\n", num);
  104.     } else {  // 没有找到
  105.         printf("没有您要删除的节点\n");
  106.     }
  107. }
  108. // 按学号顺序插入节点
  109. void link_insert_num(STU **p_head, STU *p_new) {
  110.     STU *pb, *pf;
  111.     pb = pf = *p_head;
  112.     if (*p_head == NULL) {  // 链表为空链表
  113.         *p_head = p_new;
  114.         p_new->next = NULL;
  115.         return;
  116.     }
  117.     while ((p_new->num >= pb->num) && (pb->next != NULL)) {
  118.         pf = pb;
  119.         pb = pb->next;
  120.     }
  121.     if (p_new->num < pb->num) {  // 找到一个节点的num比新来的节点num大,插在pb的前面
  122.         if (pb == *p_head) {  // 找到的节点是头节点,插在最前面
  123.             p_new->next = *p_head;
  124.             *p_head = p_new;
  125.         } else {
  126.             pf->next = p_new;
  127.             p_new->next = pb;
  128.         }
  129.     } else {  // 没有找到pb的num比p_new->num大的节点,插在最后
  130.         pb->next = p_new;
  131.         p_new->next = NULL;
  132.     }
  133. }
  134. // 链表排序(按学号从小到大)
  135. void link_order(STU *head) {
  136.     STU *pf, *pb, temp;
  137.     pf = head;
  138.     if (head == NULL) {
  139.         printf("链表为空,不用排序\n");
  140.         return;
  141.     }
  142.     if (head->next == NULL) {
  143.         printf("只有一个节点,不用排序\n");
  144.         return;
  145.     }
  146.     while (pf->next != NULL) {  // 以pf指向的节点为基准节点
  147.         pb = pf->next;  // pb从基准元素的下个元素开始
  148.         while (pb != NULL) {
  149.             if (pf->num > pb->num) {
  150.                 // 交换数据域,不改变指针结构
  151.                 temp.num = pb->num;
  152.                 temp.score = pb->score;
  153.                 strcpy(temp.name, pb->name);
  154.                
  155.                 pb->num = pf->num;
  156.                 pb->score = pf->score;
  157.                 strcpy(pb->name, pf->name);
  158.                
  159.                 pf->num = temp.num;
  160.                 pf->score = temp.score;
  161.                 strcpy(pf->name, temp.name);
  162.             }
  163.             pb = pb->next;
  164.         }
  165.         pf = pf->next;
  166.     }
  167. }
  168. // ==================== 双向链表操作函数 ====================
  169. // 双向链表创建(尾插法)
  170. void double_link_creat_head(DSTU **p_head, DSTU *p_new) {
  171.     DSTU *p_mov = *p_head;
  172.     if (*p_head == NULL) {  // 当第一次加入链表为空时,head指向p_new
  173.         *p_head = p_new;
  174.         p_new->front = NULL;
  175.         p_new->next = NULL;
  176.     } else {  // 第二次及以后加入链表
  177.         while (p_mov->next != NULL) {
  178.             p_mov = p_mov->next;  // 找到原有链表的最后一个节点
  179.         }
  180.         p_mov->next = p_new;  // 将新申请的节点加入链表
  181.         p_new->front = p_mov;
  182.         p_new->next = NULL;
  183.     }
  184. }
  185. // 双向链表遍历(正向和反向)
  186. void double_link_print(DSTU *head) {
  187.     DSTU *pb;
  188.     pb = head;
  189.     printf("\n=== 双向链表正向遍历 ===\n");
  190.     while (pb != NULL) {
  191.         printf("学号=%d 分数=%d 姓名:%s\n", pb->num, pb->score, pb->name);
  192.         if (pb->next == NULL) break;  // 记录最后一个节点
  193.         pb = pb->next;
  194.     }
  195.     printf("\n=== 双向链表反向遍历 ===\n");
  196.     while (pb != NULL) {
  197.         printf("学号=%d 分数=%d 姓名:%s\n", pb->num, pb->score, pb->name);
  198.         pb = pb->front;
  199.     }
  200.     printf("========================\n");
  201. }
  202. // 双向链表删除节点
  203. void double_link_delete_num(DSTU **p_head, int num) {
  204.     DSTU *pb, *pf;
  205.     pb = *p_head;
  206.     if (*p_head == NULL) {  // 链表为空,不需要删除
  207.         printf("链表为空,没有您要删除的节点\n");
  208.         return;
  209.     }
  210.     while ((pb->num != num) && (pb->next != NULL)) {
  211.         pb = pb->next;
  212.     }
  213.     if (pb->num == num) {  // 找到了一个节点的num和num相同,删除pb指向的节点
  214.         if (pb == *p_head) {  // 找到的节点是头节点
  215.             if ((*p_head)->next == NULL) {  // 只有一个节点的情况
  216.                 *p_head = NULL;
  217.             } else {  // 有多个节点的情况
  218.                 *p_head = pb->next;  // main函数中的head指向下个节点
  219.                 (*p_head)->front = NULL;
  220.             }
  221.         } else {  // 要删的节点是其他节点
  222.             if (pb->next != NULL) {  // 删除中间节点
  223.                 pf = pb->front;  // 让pf指向找到的节点的前一个节点
  224.                 pf->next = pb->next;  // 前一个节点的next保存后一个节点的地址
  225.                 (pb->next)->front = pf;  // 后一个节点的front保存前一个节点的地址
  226.             } else {  // 删除尾节点
  227.                 pf = pb->front;
  228.                 pf->next = NULL;
  229.             }
  230.         }
  231.         free(pb);  // 释放找到的节点
  232.         printf("删除学号为 %d 的节点成功\n", num);
  233.     } else {  // 没找到
  234.         printf("没有您要删除的节点\n");
  235.     }
  236. }
  237. // ==================== 主函数和菜单系统 ====================
  238. void print_menu() {
  239.     printf("\n========== 链表操作演示程序 ==========\n");
  240.     printf("1. 单向链表操作\n");
  241.     printf("2. 双向链表操作\n");
  242.     printf("0. 退出程序\n");
  243.     printf("====================================\n");
  244.     printf("请选择操作: ");
  245. }
  246. void print_single_menu() {
  247.     printf("\n========== 单向链表操作菜单 ==========\n");
  248.     printf("1. 创建链表\n");
  249.     printf("2. 遍历链表\n");
  250.     printf("3. 查找节点(按学号)\n");
  251.     printf("4. 查找节点(按姓名)\n");
  252.     printf("5. 插入节点(按学号排序)\n");
  253.     printf("6. 删除节点(按学号)\n");
  254.     printf("7. 链表排序\n");
  255.     printf("8. 释放链表\n");
  256.     printf("0. 返回主菜单\n");
  257.     printf("====================================\n");
  258.     printf("请选择操作: ");
  259. }
  260. void print_double_menu() {
  261.     printf("\n========== 双向链表操作菜单 ==========\n");
  262.     printf("1. 创建链表\n");
  263.     printf("2. 遍历链表(正向+反向)\n");
  264.     printf("3. 删除节点(按学号)\n");
  265.     printf("0. 返回主菜单\n");
  266.     printf("====================================\n");
  267.     printf("请选择操作: ");
  268. }
  269. void single_list_operations() {
  270.     STU *head = NULL, *p_new = NULL, *result = NULL;
  271.     int choice, num, score, delete_num;
  272.     char name[20], search_name[20];
  273.    
  274.     while (1) {
  275.         print_single_menu();
  276.         scanf("%d", &choice);
  277.         
  278.         switch (choice) {
  279.             case 1:  // 创建链表
  280.                 printf("请输入链表初始个数: ");
  281.                 scanf("%d", &num);
  282.                 for (int i = 0; i < num; i++) {
  283.                     p_new = (STU*)malloc(sizeof(STU));
  284.                     printf("请输入第%d个节点的学号、分数、姓名: ", i+1);
  285.                     scanf("%d %d %s", &p_new->num, &p_new->score, p_new->name);
  286.                     link_creat_head(&head, p_new);
  287.                 }
  288.                 printf("链表创建成功!\n");
  289.                 break;
  290.                
  291.             case 2:  // 遍历链表
  292.                 link_print(head);
  293.                 break;
  294.                
  295.             case 3:  // 按学号查找
  296.                 printf("请输入要查找的学号: ");
  297.                 scanf("%d", &num);
  298.                 result = link_search_num(head, num);
  299.                 if (result != NULL) {
  300.                     printf("找到节点: 学号=%d 分数=%d 姓名=%s\n",
  301.                            result->num, result->score, result->name);
  302.                 } else {
  303.                     printf("未找到学号为 %d 的节点\n", num);
  304.                 }
  305.                 break;
  306.                
  307.             case 4:  // 按姓名查找
  308.                 printf("请输入要查找的姓名: ");
  309.                 scanf("%s", search_name);
  310.                 result = link_search_name(head, search_name);
  311.                 if (result != NULL) {
  312.                     printf("找到节点: 学号=%d 分数=%d 姓名=%s\n",
  313.                            result->num, result->score, result->name);
  314.                 } else {
  315.                     printf("未找到姓名为 %s 的节点\n", search_name);
  316.                 }
  317.                 break;
  318.                
  319.             case 5:  // 插入节点
  320.                 p_new = (STU*)malloc(sizeof(STU));
  321.                 printf("请输入要插入节点的学号、分数、姓名: ");
  322.                 scanf("%d %d %s", &p_new->num, &p_new->score, p_new->name);
  323.                 link_insert_num(&head, p_new);
  324.                 printf("节点插入成功!\n");
  325.                 break;
  326.                
  327.             case 6:  // 删除节点
  328.                 printf("请输入要删除的学号: ");
  329.                 scanf("%d", &delete_num);
  330.                 link_delete_num(&head, delete_num);
  331.                 break;
  332.                
  333.             case 7:  // 链表排序
  334.                 link_order(head);
  335.                 printf("链表排序完成!\n");
  336.                 break;
  337.                
  338.             case 8:  // 释放链表
  339.                 link_free(&head);
  340.                 printf("链表已释放!\n");
  341.                 break;
  342.                
  343.             case 0:  // 返回主菜单
  344.                 if (head != NULL) {
  345.                     link_free(&head);
  346.                 }
  347.                 return;
  348.                
  349.             default:
  350.                 printf("无效选择,请重新输入!\n");
  351.         }
  352.     }
  353. }
  354. void double_list_operations() {
  355.     DSTU *head = NULL, *p_new = NULL;
  356.     int choice, num, delete_num;
  357.    
  358.     while (1) {
  359.         print_double_menu();
  360.         scanf("%d", &choice);
  361.         
  362.         switch (choice) {
  363.             case 1:  // 创建链表
  364.                 printf("请输入链表初始个数: ");
  365.                 scanf("%d", &num);
  366.                 for (int i = 0; i < num; i++) {
  367.                     p_new = (DSTU*)malloc(sizeof(DSTU));
  368.                     printf("请输入第%d个节点的学号、分数、姓名: ", i+1);
  369.                     scanf("%d %d %s", &p_new->num, &p_new->score, p_new->name);
  370.                     double_link_creat_head(&head, p_new);
  371.                 }
  372.                 printf("双向链表创建成功!\n");
  373.                 break;
  374.                
  375.             case 2:  // 遍历链表
  376.                 double_link_print(head);
  377.                 break;
  378.                
  379.             case 3:  // 删除节点
  380.                 printf("请输入要删除的学号: ");
  381.                 scanf("%d", &delete_num);
  382.                 double_link_delete_num(&head, delete_num);
  383.                 break;
  384.                
  385.             case 0:  // 返回主菜单
  386.                 // 释放双向链表内存
  387.                 while (head != NULL) {
  388.                     DSTU *temp = head;
  389.                     head = head->next;
  390.                     free(temp);
  391.                 }
  392.                 return;
  393.                
  394.             default:
  395.                 printf("无效选择,请重新输入!\n");
  396.         }
  397.     }
  398. }
  399. int main() {
  400.     int choice;
  401.    
  402.     printf("欢迎使用链表基础知识演示程序!\n");
  403.     printf("本程序实现了博客中介绍的所有链表操作功能\n");
  404.    
  405.     while (1) {
  406.         print_menu();
  407.         scanf("%d", &choice);
  408.         
  409.         switch (choice) {
  410.             case 1:
  411.                 single_list_operations();
  412.                 break;
  413.             case 2:
  414.                 double_list_operations();
  415.                 break;
  416.             case 0:
  417.                 printf("感谢使用,程序退出!\n");
  418.                 return 0;
  419.             default:
  420.                 printf("无效选择,请重新输入!\n");
  421.         }
  422.     }
  423.    
  424.     return 0;
  425. }
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

4 天前

举报

这个好,看起来很实用
您需要登录后才可以回帖 登录 | 立即注册