zoukankan      html  css  js  c++  java
  • Linux中的经典双链表的实现

    首先上一篇博客介绍了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 ====
    "); 
    	list_for_each(pos, &person_head.list) 
    	{ 
    		pperson = list_entry(pos, struct Person, list); 
    		printf("name:%-2s, age:%d
    ", pperson->name, pperson->age); 
    	} 
    
    	// 删除节点age为20的节点
    	printf("==== delete node(age:20) ====
    ");
    	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 ====
    ");
    	list_for_each(pos, &person_head.list)
    	{
    		pperson = list_entry(pos, struct Person, list);
    		printf("name:%-2s, age:%d
    ", 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实现的代码比这多多了。我只是把精髓提取出来。再一次感叹下,高手写的代码就是不一样啊!

    生命不止,奋斗不息!
  • 相关阅读:
    【leetcode】反转字符串
    【leetcode】反转字符串 II
    053-669
    053-668
    053-667
    053-666
    053-665
    053-664
    053-663
    053-662
  • 原文地址:https://www.cnblogs.com/huzongzhe/p/6735163.html
Copyright © 2011-2022 走看看