一,前言
上一篇C工程框架_学以致用--Apple的学习笔记是设计了框架,然后子模块中添加了单链表进行练手,今天是双链表的练手,重点是结构体的创建及添加,删除和遍历。里面搜索算法,排序算法先不使用。双链表使用很广泛,我今天自己建立了双链表结构test3.c,又模拟了linux内核驱动的双链表设计test4.c。
二,实战篇
我建立的双链表如下,首尾都是NULL,使用起来和单链表差不多,这里面尾插我就要从头循环访问到尾,确实效率低。linux内核建立的围成了圈,头插和尾插效率一样。而且关于头插和尾插分层设计的很好,另外它需要配合containof来使用,通过成员体指针变量来访问到主对象结构体变量。
我的代码test3.c
/*******************************************************************************
| Project : Double linked list
| Description : create;insert;remove;search;traversal functions
| CPU and Compiler : win10 CodeBlocks MinGw32
| R E V I S I O N H I S T O R Y
|-------------------------------------------------------------------------------
| Date Version Author Description
| ---------- -------- ------ ---------------------------------------------
| 2020-04-25 01.00.00 AppleCai First version
*******************************************************************************/
/*********************
* INCLUDES
*********************/
#include
#include
#include "../typedef.h"
/**********************
* MACROS
**********************/
/* the status of borrow of book */
#define BOOKED 1
#define RETURNED 0
/**********************
* TYPEDEFS
**********************/
typedef struct _dl
{
struct _dl *pre;
struct _dl *next;
} dl_t;
typedef struct
{
uint8 bookID;
uint16 personID;
boolean bookStatus;
} systemDL_t;
typedef struct _booksystemDL_t
{
dl_t dl; /* shall be put in the top of struct */
systemDL_t obj;
void (*initbook)(struct _booksystemDL_t *);
void (*borrowbooks)(struct _booksystemDL_t *,struct _booksystemDL_t *);
void (*returnbooks)(struct _booksystemDL_t * ,uint8 );
void (*printborrowbooklist)(struct _booksystemDL_t *);
void (*searchbooks)(struct _booksystemDL_t * ,uint8 );
} booksystemDL_t;
/**********************
* GLOBAL VAR
**********************/
/**********************
* CONST VAR
**********************/
/**********************
* Functions Implement
**********************/
static void init_dlist(booksystemDL_t * head)
{
head->dl.next = NULL;
head->dl.pre = NULL;
}
static void insertDList_head(booksystemDL_t *head,booksystemDL_t *node)
{
node->dl.next = head->dl.next;
if(head->dl.next)
{
head->dl.next->pre = node;
}
node->dl.pre = head;
head->dl.next = node;
LOG_TRACE("head insert for double linked list done!");
}
static void insertdList_tail(booksystemDL_t *head,booksystemDL_t *node)
{
booksystemDL_t *p = head;
/* find the last node when p->next is null */
while(p->dl.next)
{
p = p->dl.next;
}
/* change null to new node */
p->dl.next = node;
node->dl.pre = p;
node->dl.next = NULL;
LOG_TRACE("tail insert for single list done!");
}
static void print_dlist(booksystemDL_t * head)
{
booksystemDL_t *p = (booksystemDL_t *)head->dl.next;
while(p)
{
printf("Book ID %d borrowed for person ID 0x%X\n",p->obj.bookID,p->obj.personID);
p = p->dl.next;
}
LOG_TRACE("Traversal double linked list done!");
}
static void print_dlist_backward(booksystemDL_t * head)
{
booksystemDL_t *p = (booksystemDL_t *)head; /* point to the last item */
while(p->dl.next)
{
p = p->dl.next;
}
while(p->dl.pre!=NULL)
{
printf("Book ID %d borrowed for person ID 0x%X\n",p->obj.bookID,p->obj.personID);
p = p->dl.pre;
}
LOG_TRACE("Traversal double linked list done!");
}
static void search_dlist(booksystemDL_t *head,uint8 bookid)
{
booksystemDL_t *p = (booksystemDL_t *)head->dl.next;
boolean found = 0;
while(p)
{
if(p->obj.bookID == bookid)
{
found = 1;
printf("Book ID %d is borrowed for person ID 0x%X\n",p->obj.bookID,p->obj.personID);
break;
}
p = p->dl.next;
}
if(found == 0)
{
printf("Nobody borrow book ID %d\n",bookid);
}
LOG_TRACE("Search double linked list done!");
}
#define REMOVE_FROM_DLIST(st,head,elem,item) \
st *p = head; \
st *n = (st *)p->dl.next; \
uint8 found = 0; \
while(n) \
{ \
if(n->obj.elem == item) \
{ \
found = 1; \
p->dl.next = n->dl.next; \
if(n->dl.next) \
n->dl.next->pre = p; \
free(n); \
break; \
} \
p = n; \
n = (st *)n->dl.next; \
} \
if(found == 0) \
{ \
LOG_TRACE("Can't find Book ID!"); \
}
static void removefromdlist(booksystemDL_t * head,uint8 item)
{
REMOVE_FROM_DLIST(booksystemDL_t,head,bookID,item);
}
/**
* free list from head
* @param head: head of single list
*/
static void DeInitAll_DL(booksystemDL_t * head)
{
booksystemDL_t *p = head;
booksystemDL_t *q;
while(p)
{
q = p->dl.next; /* save next */
free(p); /* free current point */
p = q; /* point to the next obj */
}
}
void dlist1(void)
{
printf("you select double linked list test example 1!\n");
/*********** Init *****************************/
booksystemDL_t *appleBooks; /* head */
appleBooks = (booksystemDL_t *)malloc(sizeof(booksystemDL_t));
appleBooks->obj.bookID = 0;
appleBooks->obj.bookStatus = RETURNED;
appleBooks->obj.personID = 0x0;
appleBooks->initbook = init_dlist;
appleBooks->borrowbooks = insertDList_head;
appleBooks->printborrowbooklist = print_dlist;
appleBooks->searchbooks = search_dlist;
appleBooks->returnbooks = removefromdlist;
/* initlize head */
printf("\n1) Initlize Apple Book System!\n");
if(appleBooks->initbook)
{
appleBooks->initbook(appleBooks);
}
/* insert node */
printf("\n2) Three person borrow books!\n");
if(appleBooks->borrowbooks)
{
uint8 i = 0;
for (i = 0;i<3;i++)
{
booksystemDL_t *customer = (booksystemDL_t *)malloc(sizeof(booksystemDL_t));
customer->obj.bookID = i+1;
customer->obj.bookStatus = BOOKED;
customer->obj.personID = 0x1001+i;
customer->initbook = init_dlist;
customer->initbook(customer);
appleBooks->borrowbooks(appleBooks,customer);
printf("Book ID %d is borrowed for person ID 0x%X\n",customer->obj.bookID,customer->obj.personID);
}
}
/* traversal */
printf("\n3) Print borrow-list of booksystem foreward!\n");
if(appleBooks->printborrowbooklist)
{
appleBooks->printborrowbooklist(appleBooks);
}
printf("\n4) Print borrow-list of booksystem backward!\n");
appleBooks->printborrowbooklist = print_dlist_backward;
if(appleBooks->printborrowbooklist)
{
appleBooks->printborrowbooklist(appleBooks);
}
/* search */
printf("\n5) Check the status of book ID 2!\n");
if(appleBooks->searchbooks)
{
appleBooks->searchbooks(appleBooks,2);
}
/* remove */
printf("\n6) Return book ID 2!\n");
if(appleBooks->returnbooks)
{
appleBooks->returnbooks(appleBooks,2);
}
printf("\n7) Print borrow-list of booksystem again!\n");
if(appleBooks->printborrowbooklist)
{
appleBooks->printborrowbooklist(appleBooks);
}
printf("\n8) Next person borrow a book!\n");
appleBooks->borrowbooks = insertdList_tail;
if(appleBooks->borrowbooks)
{
booksystemDL_t *customer = (booksystemDL_t *)malloc(sizeof(booksystemDL_t));
customer->obj.bookID = 4;
customer->obj.bookStatus = BOOKED;
customer->obj.personID = 0x1004;
customer->initbook = init_dlist;
customer->initbook(customer);
appleBooks->borrowbooks(appleBooks,customer);
}
printf("\n9) Print borrow-list of booksystem backward!\n");
if(appleBooks->printborrowbooklist)
{
appleBooks->printborrowbooklist(appleBooks);
}
DeInitAll_DL(appleBooks);
/*********** End ******************************/
printf("\nPlease select next test item[1-9],print q to exit!Please:");
}
test4.c,参考了linux的双链表风格,并且配合containof使用,所以传入参数都是直接为对象中的双链表了,这样才是链表和对象体分离,对比后才发现我之前做的并不算链表和对象数据分离,哈哈~
/*******************************************************************************
| Project : Double linked list follow Linux
| Description : create;insert;remove;search;traversal functions
| CPU and Compiler : win10 CodeBlocks MinGw32
| R E V I S I O N H I S T O R Y
|-------------------------------------------------------------------------------
| Date Version Author Description
| ---------- -------- ------ ---------------------------------------------
| 2020-04-25 01.00.00 AppleCai First version
*******************************************************************************/
/*********************
* INCLUDES
*********************/
#include
#include
#include "../typedef.h"
/**********************
* MACROS
**********************/
/* the status of borrow of book */
#define BOOKED 1
#define RETURNED 0
/* get the offset of element with the struct head */
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/* get the head address of struct */
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
/**********************
* TYPEDEFS
**********************/
typedef struct _list_head
{
struct _list_head *next, *prev;
} list_head;
typedef struct
{
uint8 bookID;
uint16 personID;
boolean bookStatus;
} systemDL_t;
typedef struct _booksystemDL_t
{
list_head dl; /* shall be put in the top of struct */
systemDL_t obj;
void (*initbook)(list_head *);
void (*borrowbooks)(list_head *node,list_head *head);
void (*returnbooks)(list_head *,uint8 );
void (*printborrowbooklist)(list_head *);
//void (*printborrowbooklist)(struct _booksystemDL_t *);
void (*searchbooks)(struct _booksystemDL_t *,uint8 );
} booksystemDL_t;
/**********************
* GLOBAL VAR
**********************/
/**********************
* CONST VAR
**********************/
/**********************
* Functions Implement
**********************/
static void INIT_LIST_HEAD(list_head *list)
{
list->next = list;
list->prev = list;
}
/**
* put new between prev and next
* @param new: new node
* @param prev: prev of head
* @param next: next of head
*/
static inline void _list_add(list_head *new,list_head *prev,list_head *next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
/**
* add new node after head
* @param new: new node
* @param head: head of double linked list
*/
static inline void list_add(list_head *new, list_head *head)
{
_list_add(new,head,head->next);
LOG_TRACE("Insert in the head!");
}
/**
* add new node before head, means put the new node at the end of list
* @param new: new node
* @param head: head of double linked list
*/
static inline void list_add_tail(list_head *new, list_head *head)
{
_list_add(new,head->prev,head);
LOG_TRACE("Insert at tail!");
}
/* traversal */
#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)
static inline void __list_del(list_head * prev, list_head * next)
{
next->prev = prev;
prev->next = next;
}
static inline void __list_del_entry(list_head *entry)
{
__list_del(entry->prev, entry->next);
}
/**
* delete entry between prev.
*/
static inline void list_del_init(list_head *entry)
{
__list_del_entry(entry);
INIT_LIST_HEAD(entry);
LOG_TRACE("remove from double linked list done!");
}
static void print_dlist(list_head * head)
{
list_head *p;
booksystemDL_t *objhead;
/* search all the double linked list */
list_for_each(p,head)
{
/* get the head of struct */
objhead = container_of(p, booksystemDL_t, dl);
printf("Book ID %d borrowed for person ID 0x%X\n",objhead->obj.bookID,objhead->obj.personID);
}
LOG_TRACE("Traversal double linked list done!");
}
static void print_dlist1(booksystemDL_t * head)
{
booksystemDL_t *p =head;
while(p->dl.next != head)
{
printf("Book ID %d borrowed for person ID 0x%X\n",p->obj.bookID,p->obj.personID);
p = p->dl.next;
}
LOG_TRACE("Traversal double linked list done!");
}
static removefromdlist(list_head *head,uint8 bookid)
{
list_head *p;
booksystemDL_t *objhead;
list_for_each(p, head)
{
objhead = container_of(p, booksystemDL_t, dl);
if(objhead->obj.bookID == bookid)
{
list_del_init(p);
free(objhead);
break; /* if don't use break, we shall use list_for_each_safe */
}
}
}
static DeInitAll_DL(list_head *head)
{
list_head *p,*next;
booksystemDL_t *objhead;
list_for_each_safe(p, next, head) /* due to we shall release p, so use safe */
{
objhead = container_of(p, booksystemDL_t, dl);
list_del_init(p);
free(objhead);
}
}
void dlist2(void)
{
printf("you select double linked list test example 2!\n");
/*********** Init *****************************/
booksystemDL_t *appleBooks; /* head */
appleBooks = (booksystemDL_t *)malloc(sizeof(booksystemDL_t));
appleBooks->obj.bookID = 0;
appleBooks->obj.bookStatus = RETURNED;
appleBooks->obj.personID = 0x0;
appleBooks->initbook = INIT_LIST_HEAD;
appleBooks->borrowbooks = list_add_tail;
appleBooks->printborrowbooklist = print_dlist;
appleBooks->returnbooks = removefromdlist;
/* initlize head */
printf("\n1) Initlize Apple Book System!\n");
if(appleBooks->initbook)
{
appleBooks->initbook(&(appleBooks->dl));
}
/* insert node */
printf("\n2) Three person borrow books!\n");
if(appleBooks->borrowbooks)
{
uint8 i = 0;
for (i = 0; i<3; i++)
{
booksystemDL_t *customer = (booksystemDL_t *)malloc(sizeof(booksystemDL_t));
customer->obj.bookID = i+1;
customer->obj.bookStatus = BOOKED;
customer->obj.personID = 0x1001+i;
appleBooks->borrowbooks(&(customer->dl),&(appleBooks->dl));
printf("Book ID %d is borrowed for person ID 0x%X\n",customer->obj.bookID,customer->obj.personID);
}
}
/* traversal */
printf("\n3) Print borrow-list of booksystem foreward!\n");
if(appleBooks->printborrowbooklist)
{
appleBooks->printborrowbooklist(&(appleBooks->dl));
}
/* remove */
printf("\n4) Return book ID 2!\n");
if(appleBooks->returnbooks)
{
appleBooks->returnbooks(&(appleBooks->dl),6);
}
printf("\n5) Print borrow-list of booksystem again!\n");
if(appleBooks->printborrowbooklist)
{
appleBooks->printborrowbooklist(&(appleBooks->dl));
}
/* insert after head */
printf("\n6) Next person borrow a book!\n");
appleBooks->borrowbooks = list_add;
if(appleBooks->borrowbooks)
{
uint8 i = 0;
booksystemDL_t *customer = (booksystemDL_t *)malloc(sizeof(booksystemDL_t));
customer->obj.bookID = 4;
customer->obj.bookStatus = BOOKED;
customer->obj.personID = 0x1004;
appleBooks->borrowbooks(&(customer->dl),&(appleBooks->dl));
printf("Book ID %d is borrowed for person ID 0x%X\n",customer->obj.bookID,customer->obj.personID);
}
printf("\n7) Print borrow-list of booksystem again!\n");
if(appleBooks->printborrowbooklist)
{
appleBooks->printborrowbooklist(&(appleBooks->dl));
}
printf("\n8) free memory!\n");
DeInitAll_DL(&(appleBooks->dl));
/*********** End ******************************/
printf("\nPlease select next test item[1-9],print q to exit!Please:");
}
三,学习篇
3.1. qemu中的双链表设计
又看了qemu虚化代码中双链表的设计,它设计的比较特别。head没有next只有last。之后的node就是标准的pre和next。
#define Q_TAILQ_HEAD(name, type, qual) \
struct name { \
qual type *tqh_first; /* first element */ \
qual type *qual *tqh_last; /* addr of last next element */ \
}
#define Q_TAILQ_ENTRY(type, qual) \
struct { \
qual type *tqe_next; /* next element */ \
qual type *qual *tqe_prev; /* address of previous next element */\
}
它这样的设计,last直接指向尾巴(head)->tqh_last = &(elm)->field.tqe_next;
,且一开始尾巴NULL的值就被设置为了待插入节点*(head)->tqh_last = (elm);
,所以尾插效率比我自己做的要高,另外,它不用containof来找主对象,所以采用了elm和field的参数方式,这样的方式我在单链表中也用过这类用法。
#define QTAILQ_INSERT_TAIL(head, elm, field) do { \
(elm)->field.tqe_next = NULL; \
(elm)->field.tqe_prev = (head)->tqh_last; \
*(head)->tqh_last = (elm); \
(head)->tqh_last = &(elm)->field.tqe_next; \
} while (/*CONSTCOND*/0)
我的代码再演进下应该就是qemu这样的设计,然后最好的设计当然是linux内核这样的设计。
3.2 littlevgl的双链表设计
它的设计和我类似,又说到尾插的优化问题,他是添加了n_size,然后要找位置就直接#define LL_PREV_P_OFFSET(ll_p) (ll_p->n_size)
,但是一个头插_lv_ll_ins_head
函数就要调用3个小函数,感觉设计的好复杂,不喜欢。而且他的头插就是直接把new节点插入在head的前面node_set_prev(ll_p, ll_p->head, n_new);
然后重置head的地址,直接指向newll_p->head = n_new;
这个和之前理解的头插不改变head的地址是不同的。所以看不惯,哈哈~
/** Description of a linked list*/
typedef struct {
uint32_t n_size;
lv_ll_node_t * head;
lv_ll_node_t * tail;
} lv_ll_t;
/**
* Return with the pointer of the previous node after 'n_act'
* @param ll_p pointer to linked list
* @param n_act pointer a node
* @return pointer to the previous node
*/
void * _lv_ll_get_prev(const lv_ll_t * ll_p, const void * n_act)
{
/*Pointer to the prev. node is stored in the end of this node.
*Go there and return the address found there*/
const lv_ll_node_t * n_act_d = n_act;
n_act_d += LL_PREV_P_OFFSET(ll_p);
return *((lv_ll_node_t **)n_act_d);
}
/**
* Set the previous node pointer of a node
* @param ll_p pointer to linked list
* @param act pointer to a node which prev. node pointer should be set
* @param prev pointer to a node which should be the previous node before 'act'
*/
static void node_set_prev(lv_ll_t * ll_p, lv_ll_node_t * act, lv_ll_node_t * prev)
{
if(act == NULL) return; /*Can't set the prev node of `NULL`*/
uint8_t * act8 = (uint8_t *)act;
act8 += LL_PREV_P_OFFSET(ll_p);
lv_ll_node_t ** act_node_p = (lv_ll_node_t **) act8;
lv_ll_node_t ** prev_node_p = (lv_ll_node_t **) &prev;
*act_node_p = *prev_node_p;
}
/**
* Set the 'next node pointer' of a node
* @param ll_p pointer to linked list
* @param act pointer to a node which next node pointer should be set
* @param next pointer to a node which should be the next node before 'act'
*/
static void node_set_next(lv_ll_t * ll_p, lv_ll_node_t * act, lv_ll_node_t * next)
{
if(act == NULL) return; /*Can't set the next node of `NULL`*/
uint8_t * act8 = (uint8_t *)act;
act8 += LL_NEXT_P_OFFSET(ll_p);
lv_ll_node_t ** act_node_p = (lv_ll_node_t **) act8;
lv_ll_node_t ** next_node_p = (lv_ll_node_t **) &next;
*act_node_p = *next_node_p;
}
/**
* Add a new head to a linked list
* @param ll_p pointer to linked list
* @return pointer to the new head
*/
void * _lv_ll_ins_head(lv_ll_t * ll_p)
{
lv_ll_node_t * n_new;
n_new = lv_mem_alloc(ll_p->n_size + LL_NODE_META_SIZE);
if(n_new != NULL) {
node_set_prev(ll_p, n_new, NULL); /*No prev. before the new head*/
node_set_next(ll_p, n_new, ll_p->head); /*After new comes the old head*/
if(ll_p->head != NULL) { /*If there is old head then before it goes the new*/
node_set_prev(ll_p, ll_p->head, n_new);
}
ll_p->head = n_new; /*Set the new head in the dsc.*/
if(ll_p->tail == NULL) { /*If there is no tail (1. node) set the tail too*/
ll_p->tail = n_new;
}
}
return n_new;
}
3.3 nuttx OS的双链表设计
nuttx OS中双链表的数据结构设计和qemu类似,head和node不同,node就是普通的pre(上一个)和next(下一个),而head是head(头)和tail(尾巴)queue->tail = node;
。这样的代码还是比较清爽的,比qemu看起来舒服
struct dq_entry_s
{
FAR struct dq_entry_s *flink;
FAR struct dq_entry_s *blink;
};
typedef struct dq_entry_s dq_entry_t;
struct sq_queue_s
{
FAR sq_entry_t *head;
FAR sq_entry_t *tail;
};
typedef struct sq_queue_s sq_queue_t;
/****************************************************************************
* Name: dq_addlast
*
* Description:
* dq_addlast adds 'node' to the end of 'queue'
*
****************************************************************************/
void dq_addlast(FAR dq_entry_t *node, dq_queue_t *queue)
{
node->flink = NULL;
node->blink = queue->tail;
if (!queue->head)
{
queue->head = node;
queue->tail = node;
}
else
{
queue->tail->flink = node;
queue->tail = node;
}
}
/****************************************************************************
* Name: dq_addfirst
*
* Description:
* dq_addfirst affs 'node' at the beginning of 'queue'
*
****************************************************************************/
void dq_addfirst(FAR dq_entry_t *node, dq_queue_t *queue)
{
node->blink = NULL;
node->flink = queue->head;
if (!queue->head)
{
queue->head = node;
queue->tail = node;
}
else
{
queue->head->blink = node;
queue->head = node;
}
}
四,总结
最近看了多个开源代码,开源代码的双链表的结构体设计大多不同,只有nuttxOS和qemu设计的类似,但也不是完全一样。估计也就这样几种思路了。总之结构体成员设计的不同,它使用起来方法也不同。总体来看我最喜欢的是linux内核中的双链表设计方式,结构简单,代码分层理解起来也容易。在我的认知中,越容易理解的代码越优秀。高手写的代码看起来都很舒服,并且通俗易懂。