通用链表的实现

一.一般链表的局限性.

在我们学习数据结构时,链表的操作大同小异,虽然数据结构使用抽象数据类型描述算法,但是实现方法的本身特点就造成了链表的基本操作和用户自定义数据类型(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\n", &((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\n", &((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\n", data_addr->a);
    }
    //printf("%p, %p \n", 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\n", data_addr->a);
    }
    return 0;
}

五.结语

并没有什么结语

时间: 2024-08-29 11:27:18

通用链表的实现的相关文章

C 封装一个通用链表 和 一个简单字符串开发库

引言 这里需要分享的是一个 简单字符串库和 链表的基库,代码也许用到特定技巧.有时候回想一下, 如果我读书的时候有人告诉我这些关于C开发的积淀, 那么会走的多直啊.刚参加工作的时候做桌面开发, 服务是C++写,界面是C#写.那时候刚进去评级我是中级,因为他问我关于系统锁和信号量都答出来.开发一段 时间,写C#也写的很溜.后面招我那个人让我转行就写C++和php,那时候就开始学习C++有关知识. 后面去四川工作了,开发安卓,用eclipse + java语法 + android jdk,开发前端,

C语言的通用链表

在操作系统编程中, 往往是使用C语言, 但C使用起来极为痛苦, 不像C++有方便的STL模板库使用. linux内核中,有一套非常神奇的通用链表结构,能够方便的使用,管理各种类型的数据,我们今天就来研究一下,内核中的C数据结构. 本文参考:[深入分析 Linux 内核链表] 首先,我们的目标是构建一个循环双链表结构,为何是双链表,还要循环,当然是从易用性考虑了,双链表能够方便的得知自己的上一个元素,在内核中管理数据更为方便. 其一般结构大概是这样: 实现机制 首先定义一个list_node结构,

C提高 7 &nbsp; 单向链表,传统链表,通用链表,linus天才哲学代码

待定!

企业级通用链表雏形

关键数据结构: typedef struct tag_node { struct tag_node *next; }node_t; typedef struct tag_head { node_t  linker; int length; }head_t, list_t; 业务数据结构: typedef struct tag_data { node_t link; int data1; int data2; //...... }data_t; 原文地址:https://www.cnblogs.c

链表(2)

:first-child{margin-top:0!important}img.plugin{box-shadow:0 1px 3px rgba(0,0,0,.1);border-radius:3px}iframe{border:0}figure{-webkit-margin-before:0;-webkit-margin-after:0;-webkit-margin-start:0;-webkit-margin-end:0}kbd{border:1px solid #aaa;-moz-bord

C语言通用双向循环链表操作函数集

说明 相比Linux内核链表宿主结构可有多个链表结构的优点,本函数集侧重封装性和易用性,而灵活性和效率有所降低.     可基于该函数集方便地构造栈或队列集.     本函数集暂未考虑并发保护. 一  概念 链表是一种物理存储单元上非连续.非顺序的存储结构,数据元素的逻辑顺序通过链表中的指针链接次序实现.链表由一系列存储结点组成,结点可在运行时动态生成.每个结点均由两部分组成,即存储数据元素的数据域和存储相邻结点地址的指针域.当进行插入或删除操作时,链表只需修改相关结点的指针域即可,因此相比线性

Redis学习——链表实现

0. 前言 Redis 中的链表是以通用链表的形式实现的,而对于链表的用途来说,主要的功能就是增删改查,所以对于查找来说,redis其提供了一个match函数指针,用户负责实现其具体的匹配操作,从而实现通用化. 涉及的文件:adlist.h/adlist.c 1. 数据结构 typedef struct listNode { struct listNode *prev; struct listNode *next; void *value; //通用实现,可以存放任意类型的数据 } listNo

内核链表设计

1.编写一个内核模块,在模块中完成内核链表的创建.插入.删除.遍历等操作. 背景知识 1.内核链表实例分析 内核链表最大的特点是它的通用性,不必因为结构体中数据域的不同而单独为操作链表设计一套方法. Linux内核在linux/lish.h文件中定义了内核通用链表list_head类型基本结构: struct list_head {struct list_head *next,*prev;} 内核链表的常用用途是将某一个数据结构本身串成链表,或将某些链表与一个数据结构联系起来,下面通过实例来说明

谈谈C语言的数据类型

本文摘要: 本文主要讲述C语言中的数据类型,从基本的数据类型到派生的数据类型,从int ,char ,float double ....到指针,数组,函数,指向指针的指针,指向数组的指针,指向函数的指针,指针与数组的区别,指针作为函数参数,函数作为函数参数.作为例子,本文将通过通用链表结构来说明void*如何实现通用结构设计,通过相对通用的哈希结构来说明如何利用函数指针作为函数的参数以及如何在结构体中封装函数指针以实现相当于类的功能结构. 首先,通过一些常见的声明来开始本文,这些声明几乎包含本文