链表在我们的实际开发项目中有着广泛的应用,Linux内核实现了一个精妙的链表,可以方便地嵌入到任何一个数据结构中,而不用为每个数据结构单独写一个插入、删除等操作。
本文参照内核链表实现,实现了一个更加精炼的链表实现,剔除了不常用的操作,保留最常用的插入、删除、遍历操作,重写或修改了链表结构、插入删除函数,使实现更为精炼、清晰。
嵌入位置可以放置在开发者自定义结构体的任何位置,通过该节点的地址和在结构体中的偏移可以定位自定义结构体的地址:
/* 获取自定义结构体的地址 */
#define LIST_ENTRY(ptr, type, member) \
((type *)((char *)ptr - (size_t)&((type *)0)->member))
文末测试代码嵌入位置放在了结构体的起始位置,由于结构体第一个变量的地址和整体结构体的地址一致,这样方便我们从节点类型到结构体类型的转换,具体见下文的测试用例。
链表和结构体定义:
typedef struct node { /* node of a linked list. */
struct node* next; /* Points at the next node in the list */
struct node* previous; /* Points at the previous node in the list */
} NODE, LIST;
链表相关的宏定义:
#define HEAD next /* first node in list */
#define TAIL previous /* last node in list */
#define LIST_FOR_EACH(type,var,list) \
for(var = (type*)lstFirst(list); \
var != (type*)(list); \
var=(type*)lstNext(&var->node))
/* initializes a list descriptor */
#define LIST_HEAD_INIT(name) { &(name), &(name) }
实现如下:
/* initializes a list descriptor */
void lstInit(LIST* pList){
pList->HEAD = pList;
pList->TAIL = pList;
}
/* find first node in list */
NODE* lstFirst(LIST* pList){
return (pList->HEAD);
}
/* find last node in a list */
NODE* lstLast(LIST* pList){
return (pList->TAIL);
}
/* find the next node in a list */
NODE *lstNext(NODE *pNode){
return (pNode->next);
}
/* get the list empty status */
int lstEmpty(LIST* pList){
return pList->HEAD == pList;
}
/* report the number of nodes in a list */
int lstCount(LIST *pList){
int count = 0;
NODE *node = lstFirst(pList);
for (; node != pList; node = lstNext(node), count++);
return count;
}
/* insert a node in a list after a specified node */
void lstInsertAfter(NODE* pre, NODE* _new){
NODE *next = pre->next;
pre->next = _new;
_new->next = next;
_new->previous = pre;
next->previous = _new;
}
/* add a node to the end of a list */
void lstAdd(NODE* list, NODE* new){
lstInsertAfter(list->TAIL, new);
}
void __lstDelete(NODE *prev, NODE *next){
next->previous = prev;
prev->next = next;
}
/* delete a specified node from a list */
void lstDelete(NODE* entry){
__lstDelete(entry->previous, entry->next);
entry->next = NULL;
entry->previous = NULL;
}
下面我们写一个测试用例,用来测试上述链表。
首先随便模拟一个数据结构。
typedef struct __DATA__ {
NODE node;
int id;
char data[6];
}DATA;
接下来定义4个数据,孤立的,还未用链表串起来。
static DATA data0 = {
.node = LIST_HEAD_INIT(data0.node),
.id = 0,
.data = "data0"
};
static DATA data1 = {
.node = LIST_HEAD_INIT(data1.node),
.id = 1,
.data = "data1"
};
static DATA data2 = {
.node = LIST_HEAD_INIT(data2.node),
.id = 2,
.data = "data2"
};
static DATA data3 = {
.node = LIST_HEAD_INIT(data3.node),
.id = 3,
.data = "data3"
};
main
函数如下:
int main(){
DATA *var = NULL;
static LIST list = LIST_HEAD_INIT(list); /* or use lstInit() to initialize */
//lstInit(&list);
/* test add elemens */
#if 1
/* add to tail */
lstAdd(&list, (NODE*) &data0);
lstAdd(&list, (NODE*) &data1);
lstAdd(&list, (NODE*) &data2);
lstAdd(&list, (NODE*) &data3);
#else
/* add to front */
lstInsertAfter(&list, (NODE*) &data0);
lstInsertAfter(&list, (NODE*) &data1);
lstInsertAfter(&list, (NODE*) &data2);
lstInsertAfter(&list, (NODE*) &data3);
#endif
printAddr(&list);
printf("%d elemens in the list [%p]:\n", lstCount(&list), &list);
LIST_FOR_EACH(DATA, var, &list){
printf("id:%d\t%s\t%p\n", var->id, var->data, var);
}
printf("========================\n");
/* test deleted elements */
while (!lstEmpty(&list)){
DATA * deleted = (DATA *)lstLast(&list);
printf("id:%d\t%s ==> to be deleted\n", deleted->id, deleted->data);
lstDelete((NODE*) deleted);
printf("after deleted: left %d elemens in list\n", lstCount(&list));
LIST_FOR_EACH(DATA, var, &list){
printf("id:%d\t%s\t%p\n", var->id, var->data, var);
}
printf("========================\n");
}
return 0;
}
辅助函数printAddr
定义如下,主要答应链表地址,便于观察:
void printAddr(LIST* list){
int timer = 0;
NODE *node = NULL;
node = list;
printf("list addr:%p\n", node);
/* print 3 times rounds */
while(timer <= 3){
node = lstNext(node);
printf("%p ", node);
if (node == list) {
timer++;
printf("\n");
}
}
printf("========================\n\n");
}
编译运行,得到如下结果:
ubuntu@Cpl: ~ $ ./a.out
list addr:0x6010c0
0x601040 0x601060 0x601080 0x6010a0 0x6010c0
0x601040 0x601060 0x601080 0x6010a0 0x6010c0
0x601040 0x601060 0x601080 0x6010a0 0x6010c0
0x601040 0x601060 0x601080 0x6010a0 0x6010c0
========================
4 elemens in the list [0x6010c0]:
id:0 data0 0x601040
id:1 data1 0x601060
id:2 data2 0x601080
id:3 data3 0x6010a0
========================
id:3 data3 ==> to be deleted
after deleted: left 3 elemens in list
id:0 data0 0x601040
id:1 data1 0x601060
id:2 data2 0x601080
========================
id:2 data2 ==> to be deleted
after deleted: left 2 elemens in list
id:0 data0 0x601040
id:1 data1 0x601060
========================
id:1 data1 ==> to be deleted
after deleted: left 1 elemens in list
id:0 data0 0x601040
========================
id:0 data0 ==> to be deleted
after deleted: left 0 elemens in list
========================
参考资料:
[1] Linux/list.h