zoukankan      html  css  js  c++  java
  • 通用链表的实现

    一.一般链表的局限性.

            在我们学习数据结构时,链表的操作大同小异,虽然数据结构使用抽象数据类型描述算法,但是实现方法的本身特点就造成了链表的基本操作和用户自定义数据类型(ElemType)产生了高度的耦合,数据类型和链表的操作这种"绑定",降级了代码的重用性,每次将链表应用到新的场合时,都要修改源代码来保证链表与新的数据类型"绑定",大量的重复操作,难免会出现各种错误.我们希望有一种具有通用型性的链表,将数据类型与链表操作分离开,这就需要所谓的通用链表(姑且这样命名).

    链表的实现来源于nginx,但是源代码并非我亲自阅读,而是通过视频获知的,感谢视频作者@飞哥(虽然并不能@到..).

    视频中出现了一些小错误,我会在文中改正.

    二.何谓通用链表

    所谓通用链表 , 即链表具有与用户自定义的数据类型(节点类型)的无关性.我们可以一次编写处处使用

    优点:具有通用性,开销低,

    缺点:不负责内存管理,大量采用宏,无类型检查

    三.通用性原理.

    以前使用一般的链表,也曾有过这个烦恼,也曾思考过,如何能做到"通用",直到了解到这个方法,不禁拍案叫绝,此法步步为营,暗藏玄机,可谓智慧之结晶,c语言之佳作...(此处省略10000字)

    进入正题,首先要说明的是,完成这个链表,只需要一个头文件.

    首先,此法定义一个结构体

    typedef struct list_s List, *Plist;
    struct list_s{
        Plist prev, next;
    };

    这个结构体,就是贯穿全剧的线索.使用这个链表需要在结构体内定义一个 List类型的数据,就像这样:

    typedef struct{
        int a;
        List list;
    }TEST,*pTEST;

    整个链表的结构,就靠list串联起来

     

     其核心思想是 我们对list(红色部分)进行操作,就可以逻辑上从链表上删除,插入某个元素(所以说,内存此链表的实现无关内存管理)

    实际上,我们相当于管理了这样一个链表,这个链表的元素只是自己的前驱后后继指针

    看上去似乎不错,虽然list成员可以"携带"其他数据在链表中找到合适的位置,但是,我们如何访问这些数据呢?

    我们管理的是list成员,可以知道的信息就是list的地址,蓝色箭头指向的地方就是list成员就是地址了,要想访问数据,我们就得知道整个结构体的首地址,

    红色三角形的位置就是整个结构体的首地址了.我们不妨举个例子

    在这种情况下,data的大小可以通过 2004H - 2000H = 4H来计算,同时,这也是list成员的偏移量

    知道了偏移量,知道了list首地址,就知道了结构体的首地址,等等!似乎哪里不对- -! 你根本不知道那个2000H嘛,知道了还算个毛线!

    好吧,确实是个问题,但是下边的方法确实是好极了!

    typedef struct{
        int a;
        List list;
    }TEST,*pTEST;
    TEST t = {1234,{NULL, NULL}};
    Plist l = &t.list;
    printf("%d
    ", &((pTEST)0)->list);//获得偏移量!
    printf("%d", ((pTEST)((char*)l - (char*)&((pTEST)0)->list))->a);

    关键是获取到偏移量,剩下的就是顺水推舟了,

    &((pTEST)0)->list

    很神奇的代码,不是么,我们来分析一下吧

    0                                            将0视作地址

    ((pTEST)0                             强制转换为自定义结构体类型

    ((pTEST)0)->list                  

      &((pTEST)0)->list               取得list成员的地址

    由于地址从0开始,因此list的地址就是其在结构体中的偏移量

    了解了上述原理之后,我们就掌握了通用链表的核心方法.

    四,实现

    list.h

    #ifndef _LIST_H_
    #define _LIST_H_
    typedef struct list_s List, *Plist;
    struct list_s{
        Plist prev, next;
    };
    //初始化 h:头节点指针 
    #define ListInit(h)                            
    do{                                            
    (h)->prev = NULL;                            
    (h)->next = NULL;                            
    }while(0)
    //h:头节点指针 l:自定义结构体中List类型成员的指针 
    #define ListHeadInsert(h,l)                    
        do{                                        
            (l)->prev =  (h);                    
            (l)->next = (h)->next;                
            (h)->next = (l);                    
        }while(0)
    //
    #define ListTailInsert(h,l)                    
        do{                                        
            Plist t = h;                        
            while(t->next) t = t->next;            
            (l)->prev =  (t);                    
            (l)->next = (t)->next;                
            (t)->next = (l);                    
        }while(0)
    //l:要读取的节点的list成员的指针 
    //struct_type:自定义结构体的类型名
    //struct_field: List 类型成员的成员名称(变量名) 
    #define ListGetData(l,struct_type, struct_field)
            ((struct_type*)((char*)(l)-offsetof(struct_type,struct_field)))
    //要删除节点的list的指针 
    #define ListDelet(l)                        
        do{                                        
        if((l)->next == NULL)                    
        {                                        
            (l)->prev->next = (l)->next;        
            (l)->prev = NULL;                    
        }else{                                    
            (l)->prev->next = (l)->next;        
            (l)->next->prev = (l)->prev;        
        }                                        
        }while(0)
    
    #endif

    用于测试的代码:

    #include <stdio.h>
    #include "list.h"
    typedef struct{
        int a;
        List list;
    }TEST,*pTEST;
    int main(int argc, char** argv) {
        /*
        TEST t = {1234,{NULL, NULL}};
        Plist l = &t.list;
        //int n = &((TEST)0).list;
        &((pTEST)0)->list;
        printf("%d
    ", &((pTEST)0)->list);
        //printf("%d", ((pTEST)((char*)l - (char*)&((pTEST)0)->list))->a);
        printf("%d", ((pTEST)((char*)l - offsetof(TEST,list)))->a);
        */
        TEST test1 = {1234,{NULL, NULL}};
        TEST test2 = {2234,{NULL, NULL}};
        TEST test3 = {3234,{NULL, NULL}};
        
        List head;
        ListInit(&head);
        ListTailInsert(&head,&test1.list);
        ListTailInsert(&head,&test2.list);
        ListTailInsert(&head,&test3.list);
        
        List* temp = head.next;
        for(temp = head.next; temp != NULL; temp = temp->next) 
        {
            pTEST data_addr = ListGetData(temp,TEST,list);
            printf("%d
    ", data_addr->a);
        }
        //printf("%p, %p 
    ", test2.list.prev, test2.list.next);
        ListDelet(&test3.list);
        for(temp = head.next; temp != NULL; temp = temp->next) 
        {
            pTEST data_addr = ListGetData(temp,TEST,list);
            printf("%d
    ", data_addr->a);
        }
        return 0;
    }

    五.结语

    并没有什么结语

  • 相关阅读:
    H5项目开发分享——用Canvas合成文字
    《JavaScript设计模式 张》整理
    Linux常用指令指南,终端装逼利器
    飞起来的正则表达式
    JavaScript特性(attribute)、属性(property)和样式(style)
    使用 Nginx 提升网站访问速度
    centos 邮件服务 腾讯企业邮箱(免费) 使用iRedmail 需要有公网的centos主机 发邮件协议:smtp 端口25 收邮件协议:pop3 端口110 iredmail安装配置 使用邮箱系统 第三十一节课
    用nginx的反向代理机制解决前端跨域问题在nginx上部署web静态页面
    mysql字符集调整总结
    因为smb和nfs挂掉导致客户端开机启动不了
  • 原文地址:https://www.cnblogs.com/xdblog/p/5468101.html
Copyright © 2011-2022 走看看