奔跑的小河
Talk is cheap. Show me the code.

导航

 

首先上一篇博客介绍了Linux下的两个经典宏,它可以根据结构体中的成员变量地址,计算出结构体地址。有了它,就可以实现可复用的高效双链表。这次我再Windows环境下给予的实现,看完觉得会受益匪浅。

 

 

Linux中双向链表的使用思想

它是将双向链表节点嵌套在其它的结构体中;在遍历链表的时候,根据双链表节点的指针获取"它所在结构体的指针",从而再获取数据。
我举个例子来说明,可能比较容易理解。假设存在一个社区中有很多人,每个人都有姓名和年龄。通过双向链表将人进行关联的模型图如下:

 


person代表人,它有name和age属性。为了通过双向链表对person进行链接,我们在person中添加了list_head属性。通过list_head,我们就将person关联起来了。

 

struct person 
{ 
    int age; 
    char name[20];
    struct list_head list; 
};

 

下面是Windows下的实现:

//1.节点定义。虽然名称list_head,但是它既是双向链表的表头,也代表双向链表的节点。
struct list_head {
	struct list_head *next, *prev;
};

//2.初始化节点:将list节点的前继节点和后继节点都是指向list本身。
static inline void INIT_LIST_HEAD(struct list_head *list)
{
	list->next = list;
	list->prev = list;
}

/* 3.添加节点
list_add(newhead, prev, next)的作用是添加节点:将newhead插入到prev和next节点之间。
list_add(newhead, head)的作用是添加newhead节点:将newhead添加到head之后,是newhead称为head的后继节点。
list_add_tail(newhead, head)的作用是添加newhead节点:将newhead添加到head之前,即将newhead添加到双链表的末尾。
*/
static inline void list_add(struct list_head *newhead,struct list_head *prev,struct list_head *next)
{
	next->prev = newhead;
	newhead->next = next;
	newhead->prev = prev;
	prev->next = newhead;
}
//将newhead添加到head之后
static inline void list_add(struct list_head *newhead, struct list_head *head)
{
	list_add(newhead, head, head->next);
}
//将newhead添加到head之前,也就是末尾
static inline void list_add_tail(struct list_head *newhead, struct list_head *head)
{
	list_add(newhead, head->prev, head);
}


/* 4.删除节点
list_del(prev, next) 的作用是从双链表中删除prev和next之间的节点。
list_del(entry) 的作用是从双链表中删除entry节点。
list_del_init(entry) 的作用是从双链表中删除entry节点,并将entry节点的前继节点和后继节点都指向entry本身。
*/
static inline void list_del(struct list_head * prev, struct list_head * next)
{
	next->prev = prev;
	prev->next = next;
}
//删除自身
static inline void list_del(struct list_head *entry)
{
	list_del(entry->prev, entry->next);
}

static inline void list_del_init(struct list_head *entry)
{
	list_del_entry(entry);
	INIT_LIST_HEAD(entry);
}
//5.替换节点:list_replace(old, newhead)的作用是用newhead节点替换old节点。
static inline void list_replace(struct list_head *old,struct list_head *newhead)
{
newhead->next = old->next;
newhead->next->prev = newhead;
newhead->prev = old->prev;
newhead->prev->next = newhead;
}
//6. 判断双链表是否为空:list_empty(head)的作用是判断双链表是否为空。它是通过区分"表头的后继节点"是不是"表头本身"来进行判断的。
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
/*7. 获取节点
list_entry(ptr, type, member) 实际上是调用的container_of宏。
它的作用是:根据"结构体(type)变量"中的"域成员变量(member)的指针(ptr)"来获取指向整个结构体变量的指针。
*/
// 获得结构体(TYPE)的变量成员(MEMBER)在此结构体中的偏移量。
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
//根据"结构体(type)变量"中的"域成员变量(member)的指针(ptr)"来获取指向整个结构体变量的指针
#define container_of(PT,TYPE,MEMBER) ((TYPE *)((char *)(PT) - offsetof(TYPE,MEMBER)))

#define list_entry(ptr, type, member) container_of(ptr, type, member)

/*8.遍历节点
list_for_each(pos, head)和list_for_each_safe(pos, n, head)的作用都是遍历链表。但是它们的用途不一样!
list_for_each(pos, head)通常用于获取节点,而不能用到删除节点的场景。
list_for_each_safe(pos, n, head)通常删除节点的场景。
*/
#define list_for_each(pos, head) for (pos = (head)->next; pos != (head); pos = pos->next)

#define list_for_each_safe(pos, afterpos, head) for (pos = (head)->next, n = pos->next; pos != (head); pos = n, n = pos->next)
</pre><h2><span style="font-family:verdana,arial,helvetica,sans-serif; font-size:14px; line-height:21px">测试使用方法:</span></h2><span style="font-family:verdana,arial,helvetica,sans-serif; font-size:14px; line-height:21px"></span><pre name="code" class="cpp">#include <stdio.h> 
#include <stdlib.h>
#include <string.h>
#include "list.h" 

struct Person 
{ 
	int age; 
	char name[20];
	struct list_head list; 
};

void main() 
{ 
	Person *pperson; 
	Person person_head; //为了建立一个空的循环双链表表头,不存放任何数据
	list_head *pos, *next; 
	int i;

	// 初始化双链表的表头 
	INIT_LIST_HEAD(&person_head.list); 

	// 添加节点
	for (i=0; i<5; i++)
	{
		pperson = (Person*)malloc(sizeof(Person));
		pperson->age = (i+1)*10;
		sprintf(pperson->name, "%d", i+1);
		// 将节点链接到链表的末尾 
		list_add_tail(&(pperson->list), &(person_head.list));
	}

	// 遍历链表
	printf("==== 1st iterator d-link ====\n"); 
	list_for_each(pos, &person_head.list) 
	{ 
		pperson = list_entry(pos, struct Person, list); 
		printf("name:%-2s, age:%d\n", pperson->name, pperson->age); 
	} 

	// 删除节点age为20的节点
	printf("==== delete node(age:20) ====\n");
	list_for_each_safe(pos, next, &person_head.list)
	{
		pperson = list_entry(pos, struct Person, list); //获取双链表结构体的地址
		if(pperson->age == 20)
		{
			list_del_init(pos);
			free(pperson);
		}
	}

	// 再次遍历链表
	printf("==== 2nd iterator d-link ====\n");
	list_for_each(pos, &person_head.list)
	{
		pperson = list_entry(pos, struct Person, list);
		printf("name:%-2s, age:%d\n", pperson->name, pperson->age);
	}

	// 释放资源
	list_for_each_safe(pos, next, &person_head.list)
	{
		pperson = list_entry(pos, struct Person, list); 
		list_del_init(pos); 
		free(pperson); 
	}

}

结果:

 

 

==== 1st iterator d-link ====
name:1 , age:10
name:2 , age:20
name:3 , age:30
name:4 , age:40
name:5 , age:50
==== delete node(age:20) ====
==== 2nd iterator d-link ====
name:1 , age:10
name:3 , age:30
name:4 , age:40
name:5 , age:50


到这里就把Linux的双链表实现,当然Linux实现的代码比这多多了。我只是把精髓提取出来。再一次感叹下,高手写的代码就是不一样啊!

 

posted on 2015-07-16 19:29  奔跑的小河  阅读(276)  评论(0编辑  收藏  举报