概述
在操作系统内核中,绝大多数数据结构都是通过链表来实现的,相对于数组,链表可以很方便的用来管理数据,给数据管理带来了更多的可能性。
注意
1、不论是linux、vxWorks还是其它操作系统中内核链表都承担着绝大部分数据管理工作。
2、因为链表可以很灵活的管理数据,所以在使用和设计链表结构的时候,我们也应该尽可能提高灵活性,而不应该将链表局限于某一点或者某一类数据类型。
3、通用化的链表设计核心,在于定义链表结构的时候,只定义指针域,通过指针域完成对节点的所有处理。
4、在具体应用中,将链表结构放到具体节点中首地址(节点继承链表对象),根据继承向上造型的原则,可以将子类的指针引用和转换为基类类型的指针或引用,从而通过节点地址调用链表的一系列处理函数。
介绍
示例代码从vxWorks源码中提取而来,进行二次封装,并加入测试代码验证,可直接下载使用。
另外,linux源码中的链表可以将链表结构加入到节点任何位置,通过调用宏container_of获取节点地址。后面文章会对此进行详细阐述。
核心
通用化双向链表由前驱指针域、后继指针域,以及数据构成,数据用来存储链表元素个数,前驱指针指向尾节点,后继指针指向头指针,链表节点只包含前驱指针域和后继指针域,用于节点间的链接,如下图代码和图形所示。
/* 节点定义 */ struct t_node { struct t_node *p_prev; /* 前节点 */ struct t_node *p_next; /* 后节点 */ }; /* 链表定义 */ struct t_list { struct t_node node; /* 头结点 */ int count; /* 链表长度 */ }; #define HEAD node.p_next /* 链表头结点 */ #define TAIL node.p_prev /* 链表尾结点 */
链表的插入分为在链表头插入、链表尾插入以及链表中插入三种。
链表头插入示意图:
![]()
链表尾插入示意图:
![]()
链表中插入示意图:
![]()
插入的核心在于找到插入位置前后的节点,然后修改对应的指针指向,从而实现插入功能。具体代码如下:
/** * @在链表指定节点之后插入节点 * @p_list:链表 p_prev:指定节点 p_node:插入节点 **/ void lst_insert(struct t_list *p_list, struct t_node *p_prev, struct t_node *p_node) { struct t_node *p_next; if (p_prev == NULL) { /* 头结点插入 */ p_next = p_list->HEAD; p_list->HEAD = p_node; } else { p_next = p_prev->p_next; p_prev->p_next = p_node; } if (p_next == NULL) /* 尾节点插入 */ p_list->TAIL = p_node; else p_next->p_prev = p_node; /* 处理插入节点指针及链表长度 */ p_node->p_next = p_next; p_node->p_prev = p_prev; p_list->count++; }
链表节点删除同样分为删除头节点、删除尾节点以及删除中间节点。
删除头节点:将头结点移动到删除节点的下一个节点。
删除尾节点:将尾节点移动到删除节点的前一个节点。
删除中间节点:将删除节点的前一个节点和后一个节点直接相连。
/** * @删除链表中指定节点 * @p_list:链表 p_node:节点 **/ void lst_del(struct t_list *p_list, struct t_node *p_node) { if (p_node->p_prev == NULL) /* 头结点 */ p_list->HEAD = p_node->p_next; else p_node->p_prev->p_next = p_node->p_next; if (p_node->p_next == NULL) /* 尾节点 */ p_list->TAIL = p_node->p_prev; else p_node->p_next->p_prev = p_node->p_prev; p_list->count--; }
节点的获取操作:
1. 根据偏移量和链表长度对比,判断节点属于前半段还是后半段。
2. 若在前半段,则通过后继指针,顺序遍历。
3. 若在后半段,则通过前驱指针,逆向遍历。
/** * @获取第N个结点,不删除 * @p_list:链表 pos:节点偏移 * @返回结点 **/ struct t_node *lst_n_th(struct t_list *p_list, int pos) { struct t_node *p_node; if ((pos < 1) || (pos > p_list->count)) /* 1 <= pos <= count */ return NULL; if (pos < (p_list->count >> 1)) { /* 前半段,从头结点开始查找 */ p_node = p_list->HEAD; while (--pos > 0) p_node = p_node->p_next; } else { /* 后半段,从尾结点开始查找 */ pos -= p_list->count; p_node = p_list->TAIL; while (pos++ < 0) p_node = p_node->p_prev; } return (p_node); }
查找指定节点位置:从头节点开始遍历链表,若找到指定节点,则返回对应的偏移量,若未找到,返回-1。
/** * @查找指定节点在链表中的位置 * @p_list:链表 p_node:指定节点 * @位置(从1开始,不存在返回-1) **/ int lst_find(struct t_list *p_list, struct t_node *p_node) { struct t_node *p_next; int index = 1; p_next = lst_first(p_list); while ((p_next!=NULL) && (p_next!=p_node)) { index++; p_next = lst_next(p_next); } if (p_next == NULL) return (-1); else return (index); }
获取距离指定节点N步的节点:n<0表明在前面,通过前驱指针逆向查找,n>0表明在后面,通过后继指针顺向查找。同样,若链表长度不够,意味着节点不存在。
/** * @获取距离指定节点N步的结点,不删除 * @p_node:指定节点 n:距离,>0在后面,<0在前面 * @返回结点 **/ struct t_node *lst_n_step(struct t_node *p_node, int n) { int i; for (i=0; i<abs(n); i++) { if (n < 0) p_node = p_node->p_prev; else if (n > 0) p_node = p_node->p_next; if (p_node == NULL) break; } return (p_node); }
拼接两个链表:
1. 将目标链表的尾节点的下一个节点指向添加链表的头节点。
2. 将添加节点的头节点的前一个节点指向目标节点的尾节点。
3. 将目标节点的尾节点指向添加链表的尾节点。
4. 改变链表总长度,删除添加链表。
/** * @拼接两个链表 * @p_dst:目标链表 p_add:添加链表 **/ void lst_concat(struct t_list *p_dst, struct t_list *p_add) { if (p_add->count == 0) return; if (p_dst->count == 0) { *p_dst = *p_add; } else { p_dst->TAIL->p_next = p_add->HEAD; p_add->HEAD->p_prev = p_dst->TAIL; p_dst->TAIL = p_add->TAIL; p_dst->count += p_add->count; } lst_init(p_add); /* 清空添加链表 */ }
提取子链分为从头结点提取、提取到尾节点以及提取链表中间部分三种情况。
从头结点提取:将链表的头节点指向子链尾节点的下一个节点。
提取到尾节点:将链表的尾节点指向子链头结点的前一个节点。
提取链表中间部分:将子链头节点的前一个节点的下一个节点指向尾节点的下一个节点。并将尾节点的下一个节点的前一个节点指向头节点的前一个节点,如下图所示。
![]()
/** * @提取链表中的子链 * @p_src:链表 p_dst:子链 * @p_start: 起始节点 p_end:终止节点 **/ void lst_extract(struct t_list *p_src, struct t_node *p_start, struct t_node *p_end, struct t_list *p_dst) { int cnt = 0; struct t_node *p_node; /* 处理原链表,减去节点 */ if (p_start->p_prev == NULL) p_src->HEAD = p_end->p_next; else p_start->p_prev->p_next = p_end->p_next; if (p_end->p_next == NULL) p_src->TAIL = p_start->p_prev; else p_end->p_next->p_prev = p_start->p_prev; /* 处理子链 */ p_dst->HEAD = p_start; p_dst->TAIL = p_end; p_start->p_prev = NULL; p_end->p_next = NULL; /* 处理长度 */ for (p_node=p_start; p_node!=NULL; p_node=p_node->p_next) cnt++; p_src->count -= cnt; p_dst->count = cnt; }
示例
★包含头文件dll.h和源文件dll.c(均已验证通过)。
dll.h
/** * @Filename : dll.h * @Revision : $Revision: 1.0 $ * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅) * @Description : 通用双向链表设计 * @Explain : 基于VxWorks源代码lstLib.h中提取而来, dll = doubly linked list **/ #ifndef __DLL_H__ #define __DLL_H__ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <malloc.h> /* 节点定义 */ struct t_node { struct t_node *p_prev; /* 前节点 */ struct t_node *p_next; /* 后节点 */ }; /* 链表定义 */ struct t_list { struct t_node node; /* 头结点 */ int count; /* 链表长度 */ }; #define HEAD node.p_next /* 链表头结点 */ #define TAIL node.p_prev /* 链表尾结点 */ /** * @初始化链表 * @p_list:链表 **/ void lst_init(struct t_list *p_list); /** * @添加节点到链表尾部 * @p_list:链表 p_node:节点 **/ void lst_add(struct t_list *p_list, struct t_node *p_node); /** * @拼接两个链表 * @p_dst:目标链表 p_add:添加链表 **/ void lst_concat(struct t_list *p_dst, struct t_list *p_add); /** * @链表长度获取 * @p_list:链表 * @返回链表长度 **/ int lst_count(struct t_list *p_list); /** * @删除链表中指定节点 * @p_list:链表 p_node:节点 **/ void lst_del(struct t_list *p_list, struct t_node *p_node); /** * @提取链表中的子链 * @p_src:链表 p_dst:子链 * @p_start: 起始节点 p_end:终止节点 **/ void lst_extract(struct t_list *p_src, struct t_node *p_start, struct t_node *p_end, struct t_list *p_dst); /** * @获取链表头结点,不删除 * @p_list:链表 * @返回头结点 **/ struct t_node *lst_first(struct t_list *p_list); /** * @获取链表头结点并删除 * @p_list:链表 * @返回头结点 **/ struct t_node *lst_get(struct t_list *p_list); /** * @在链表指定节点之后插入节点 * @p_list:链表 p_prev:指定节点 p_node:插入节点 **/ void lst_insert(struct t_list *p_list, struct t_node *p_prev, struct t_node *p_node); /** * @获取链表尾结点,不删除 * @p_list:链表 * @返回尾结点 **/ struct t_node *lst_last(struct t_list *p_list); /** * @获取下一个结点,不删除 * @p_node:节点 * @返回下一个结点 **/ struct t_node *lst_next(struct t_node *p_node); /** * @获取第N个结点,不删除 * @p_list:链表 pos:节点偏移 * @返回结点 **/ struct t_node *lst_n_th(struct t_list *p_list, int pos); /** * @获取前一个结点,不删除 * @p_node:节点 * @返回前一个结点 **/ struct t_node *lst_prev(struct t_node *p_node); /** * @获取距离指定节点N步的结点,不删除 * @p_node:指定节点 n:距离,>0在后面,<0在前面 * @返回结点 **/ struct t_node *lst_n_step(struct t_node *p_node, int n); /** * @查找指定节点在链表中的位置 * @p_list:链表 p_node:指定节点 * @位置(从1开始,不存在返回-1) **/ int lst_find(struct t_list *p_list, struct t_node *p_node); /** * @销毁释放链表 * @p_list:链表 **/ void lst_free(struct t_list *p_list); #ifdef __cplusplus } #endif #endif
dll.c
/** * @Filename : dll.c * @Revision : $Revision: 1.0 $ * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅) * @Description : 通用双向链表设计 * @Explain : 基于VxWorks源代码lstLib.h中提取而来, dll = doubly linked list **/ #include "dll.h" /** * @初始化链表 * @p_list:链表 **/ void lst_init(struct t_list *p_list) { p_list->HEAD = NULL; p_list->TAIL = NULL; p_list->count = 0; } /** * @添加节点到链表尾部 * @p_list:链表 p_node:节点 **/ void lst_add(struct t_list *p_list, struct t_node *p_node) { lst_insert(p_list, p_list->TAIL, p_node); } /** * @拼接两个链表 * @p_dst:目标链表 p_add:添加链表 **/ void lst_concat(struct t_list *p_dst, struct t_list *p_add) { if (p_add->count == 0) return; if (p_dst->count == 0) { *p_dst = *p_add; } else { p_dst->TAIL->p_next = p_add->HEAD; p_add->HEAD->p_prev = p_dst->TAIL; p_dst->TAIL = p_add->TAIL; p_dst->count += p_add->count; } lst_init(p_add); /* 清空添加链表 */ } /** * @链表长度获取 * @p_list:链表 * @返回链表长度 **/ int lst_count(struct t_list *p_list) { return (p_list->count); } /** * @删除链表中指定节点 * @p_list:链表 p_node:节点 **/ void lst_del(struct t_list *p_list, struct t_node *p_node) { if (p_node->p_prev == NULL) /* 头结点 */ p_list->HEAD = p_node->p_next; else p_node->p_prev->p_next = p_node->p_next; if (p_node->p_next == NULL) /* 尾节点 */ p_list->TAIL = p_node->p_prev; else p_node->p_next->p_prev = p_node->p_prev; p_list->count--; } /** * @提取链表中的子链 * @p_src:链表 p_dst:子链 * @p_start: 起始节点 p_end:终止节点 **/ void lst_extract(struct t_list *p_src, struct t_node *p_start, struct t_node *p_end, struct t_list *p_dst) { int cnt = 0; struct t_node *p_node; /* 处理原链表,减去节点 */ if (p_start->p_prev == NULL) p_src->HEAD = p_end->p_next; else p_start->p_prev->p_next = p_end->p_next; if (p_end->p_next == NULL) p_src->TAIL = p_start->p_prev; else p_end->p_next->p_prev = p_start->p_prev; /* 处理子链 */ p_dst->HEAD = p_start; p_dst->TAIL = p_end; p_start->p_prev = NULL; p_end->p_next = NULL; /* 处理长度 */ for (p_node=p_start; p_node!=NULL; p_node=p_node->p_next) cnt++; p_src->count -= cnt; p_dst->count = cnt; } /** * @获取链表头结点,不删除 * @p_list:链表 * @返回头结点 **/ struct t_node *lst_first(struct t_list *p_list) { return (p_list->HEAD); } /** * @获取链表头结点并删除 * @p_list:链表 * @返回头结点 **/ struct t_node *lst_get(struct t_list *p_list) { struct t_node *p_node = p_list->HEAD; if (p_node == NULL) return NULL; p_list->HEAD = p_node->p_next; /* 设置第二个节点为头结点 */ if (p_node->p_next == NULL) /* 空链表 */ p_list->TAIL = NULL; else p_node->p_next->p_prev = NULL; p_list->count--; return (p_node); } /** * @在链表指定节点之后插入节点 * @p_list:链表 p_prev:指定节点 p_node:插入节点 **/ void lst_insert(struct t_list *p_list, struct t_node *p_prev, struct t_node *p_node) { struct t_node *p_next; if (p_prev == NULL) { /* 头结点插入 */ p_next = p_list->HEAD; p_list->HEAD = p_node; } else { p_next = p_prev->p_next; p_prev->p_next = p_node; } if (p_next == NULL) /* 尾节点插入 */ p_list->TAIL = p_node; else p_next->p_prev = p_node; /* 处理插入节点指针及链表长度 */ p_node->p_next = p_next; p_node->p_prev = p_prev; p_list->count++; } /** * @获取链表尾结点,不删除 * @p_list:链表 * @返回尾结点 **/ struct t_node *lst_last(struct t_list *p_list) { return (p_list->TAIL); } /** * @获取下一个结点,不删除 * @p_node:节点 * @返回下一个结点 **/ struct t_node *lst_next(struct t_node *p_node) { return (p_node->p_next); } /** * @获取第N个结点,不删除 * @p_list:链表 pos:节点偏移 * @返回结点 **/ struct t_node *lst_n_th(struct t_list *p_list, int pos) { struct t_node *p_node; if ((pos < 1) || (pos > p_list->count)) /* 1 <= pos <= count */ return NULL; if (pos < (p_list->count >> 1)) { /* 前半段,从头结点开始查找 */ p_node = p_list->HEAD; while (--pos > 0) p_node = p_node->p_next; } else { /* 后半段,从尾结点开始查找 */ pos -= p_list->count; p_node = p_list->TAIL; while (pos++ < 0) p_node = p_node->p_prev; } return (p_node); } /** * @获取前一个结点,不删除 * @p_node:节点 * @返回前一个结点 **/ struct t_node *lst_prev(struct t_node *p_node) { return (p_node->p_prev); } /** * @获取距离指定节点N步的结点,不删除 * @p_node:指定节点 n:距离,>0在后面,<0在前面 * @返回结点 **/ struct t_node *lst_n_step(struct t_node *p_node, int n) { int i; for (i=0; i<abs(n); i++) { if (n < 0) p_node = p_node->p_prev; else if (n > 0) p_node = p_node->p_next; if (p_node == NULL) break; } return (p_node); } /** * @查找指定节点在链表中的位置 * @p_list:链表 p_node:指定节点 * @位置(从1开始,不存在返回-1) **/ int lst_find(struct t_list *p_list, struct t_node *p_node) { struct t_node *p_next; int index = 1; p_next = lst_first(p_list); while ((p_next!=NULL) && (p_next!=p_node)) { index++; p_next = lst_next(p_next); } if (p_next == NULL) return (-1); else return (index); } /** * @销毁释放链表 * @p_list:链表 **/ void lst_free(struct t_list *p_list) { struct t_node *p_node, *p_mid; if (p_list->count > 0) { p_node = p_list->HEAD; while (p_node != NULL) { p_mid = p_node->p_next; free(p_node); p_node = p_mid; } p_list->count = 0; p_list->HEAD = p_list->TAIL = NULL; } }
结论
示例代码从vxWorks源码中提取而来,加入自己的分析和注释,请结合后面文章《也没想象中那么神秘的数据结构-一种通用化双向链表设计(对象设计)》和《也没想象中那么神秘的数据结构-一种通用化双向链表设计(测试例程)》测试使用。
往期 · 推荐
也没想象中那么神秘的数据结构-一环扣一环的“链表”(循环双链表)
也没想象中那么神秘的数据结构-一环扣一环的“链表”(双向链表)
我用C语言玩对象,框架化的模板模式
关注
更多精彩内容,请关注微信公众号:不只会拍照的程序猿,本人致力分享linux、设计模式、C语言、嵌入式、编程相关知识,也会抽空分享些摄影相关内容,同样也分享大量摄影、编程相关视频和源码,另外你若想要本文章源码请关注公众号:不只会拍照的程序猿,后台回复:数据结构源码,也可点击此处下载。