侵入式链表学习
在408和常见教材里面,以普通的尾指针点单链表为例, 一个链表节点包含数据部分和尾指针。首个节点称为头节点,不包含数据,它的尾指针指向下一个节点(首个节点设计成存数据的也行)。每个节点依次连接,直到最后一个节点,尾指针设为null,表示链表结束。如果设计一个节点内有首尾两个指针,那就可以让它们分别指向上一个和下一个节点,构成双向链表。
struct ListNode { int value; ListNode* next; } //包含尾指针的单链表 struct ListNode { ListNode* prev; ListNode* next; T value; }//包含首尾指针的双向链表
对于这样传统的非侵入式链表而言,是链表节点中包含数据。此外,链表上的所有节点,存的数据肯定都是相同的。与之对应的是侵入式链表,在linux里面广泛应用
这个图画的也不是完全准确,next应该被包在大框内。
这个设计看起来似乎和之前的数据结构区别不大,事实上也确实区别不大,但是指针被包在了数据内:
typedef struct list_structure { struct list_structure* next; }ListObj;//指针节点单独处理 typedef struct { int data1; ListObj list; } Node_int; //只要包含指针节点就行 typedef struct { float data2; ListObj list; } Node_float;
指针节点单独写成结构体,这样其他节点可以按照自己的需求重新写结构体,只需要在里面包含指针结构体就行。这样一来,每个节点的数据都可以按需要来,而且所有链表的操作都可以统一。
linux下链表头文件:
#ifndef _LIST_H #define _LIST_H #define offset_of(type, member) (unsigned long) &((type*)0)->member //计算结构体成员相对结构体的偏移
//使用了一个技巧,将一个空指针转换为结构体类型的指针,然后获取成员的地址
//并将其转换为unsigned long
类型。这样就获得了结构体成员相对于结构体起始地址的偏移量。
#define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offset_of(type,member) );}) //通过结构体和类型,结构体成员的地址来获取结构体的首地址 //它首先将传入的指针ptr
强制转换为指向成员的指针,
//然后使用offset_of
宏计算出成员相对于结构体的偏移量。
//最后,通过从成员地址减去偏移量的方式,得到结构体的首地址。
typedef struct list_structure { struct list_structure* next; struct list_structure* prev; }ListObj; #define LIST_HEAD_INIT(name) {&(name), &(name)} #define LIST_HEAD(name) ListObj name = LIST_HEAD_INIT(name) #define list_entry(node, type, member) \ container_of(node, type, member) //宏,通过结构体的成员及其地址获取结构体的首地址 #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) void list_init(ListObj* list); void list_insert_after(ListObj* list, ListObj* node); void list_insert_before(ListObj* list, ListObj* node); void list_remove(ListObj* node); int list_isempty(const ListObj* list); unsigned int list_len(const ListObj* list); #endif
源代码:
#include "link_list.h" void list_init(ListObj* list) { list->next = list->prev = list; } void list_insert_after(ListObj* list, ListObj* node) { list->next->prev = node; node->next = list->next; list->next = node; node->prev = list; } void list_insert_before(ListObj* list, ListObj* node) { list->prev->next = node; node->prev = list->prev; list->prev = node; node->next = list; } void list_remove(ListObj* node) { node->next->prev = node->prev; node->prev->next = node->next; node->next = node->prev = node; } int list_isempty(const ListObj* list) { return list->next == list; } unsigned int list_len(const ListObj* list) { unsigned int len = 0; const ListObj* p = list; while (p->next != list) { p = p->next; len++; } return len; }
参考来源: C语言侵入式链表 - 掘金 (juejin.cn)