c语言数据结构之通用链表

来源:互联网 发布:东软医保软件客服 编辑:程序博客网 时间:2024/05/01 05:04

这篇文章对链表这种数据结构进行深入分析和改造,常规的链表数据域的数据类型都是固定的,借助void*通用指针可以实现通用型的链表,链表的数据域是可以存放任何数据类型,而不必每次都将数据类型固定下来,但是要求链表的数据要一致,不能同时存储多种数据类型。这篇文章是学习了一篇微信公众号文章,对其中的代码进行复现,文末会贴出该公众号的链接,并表示对作者的感谢和敬佩!
首先构造链表节点数据类型,每一个节点包含除了数据域、指针域还包含一个标识唯一节点的id;接着,将节点、头指针和与节点相关的操作(输出链表元素、销毁链表等)作为回调函数绑定在一起封装成一个链表类型。代码如下:

//链表节点数据类型--结构体typedef struct linknode{    int id;    //指向通用数据类型的指针作数据域    void *data;     struct linknode *next;} linknode;//定义链表数据结构,将头节点和与链表相关的操作组织在一起typedef struct linklist{    //头节点    linknode *head;    //回调函数,获取数据唯一标识的指针函数    int (*get_id)(void *);    //回调函数,显示数据的指针函数    void (*display)(void *);    //回调函数,销毁数据的指针函数    void (*destructor)(void *);}linklist;

void *data; 作为数据域存储指向一个不确定类型的指针达到实现通用链表的作用,通用指针所占的地址空间是固定的,基于此就有了实现通用链表的可能,回调函数的实现是通过指向函数的指针来实现。接着通过如下代码进行链表的初始化,生成一个通用链表:

linklist* linklist_init(int (*get_id)(void *),void (*display)(void *),void (*destructor)(void *)){    //申请内存空间,存放链表    linklist *l = malloc(sizeof(linklist));    l->head = NULL;    l->get_id = get_id;    l->display = display;    l->destructor = destructor;    return l; }

构建链表函数,返回指向链表的指针,将指向回调函数的指针作为链表初始化函数的形参传给链表。因为通用链表的数据域存储的是指向数据的指针,所以销毁链表会有三层:1、销毁linknode 的data 2、销毁linklist的每一个linknode 3、销毁整个linklist

void linklist_des(linklist *l){    //获取头节点,作为循环销毁linknode的起点    linknode *p = l->head;    //循环销毁linknode 的data 和 linknode本身    while(p!=NULL){        linknode *pNext = p->next;        //销毁节点数据 data ,因为通用链表的data可以指向不同数据类型,所以必须单独实现销毁data对应的数据类型的方法,        //即destructor,因一个链表存储的数据类型都是相同的,destructor是链表级别的,是linklist的成员函数        l->destructor(p->data);        //释放指向linknode的指针        free(p);        p = pNext;    }    //释放指向linklist的指针    free(l);}

如上代码所示,每一个链表节点的数据类型是不固定的,甚至是结构体类型,所以销毁这不固定的方法也是需要在初始化链表时自定义的,用对应destructor去销毁相应的data。接着释放指向节点的指针和指向链表的指针。接下来分别实现链表的查、插、删、显操作,代码如下:

//链表中插入节点void linklist_insert(linklist *l,void *data){    //获得id    int id = l->get_id(data);    //申请内存    linknode *newNode = malloc(sizeof(linknode));    newNode->id = id;    newNode->data = data;    newNode->next = NULL;    //二级指针,寻找id的“下一个”节点,遍历链表使用指针不用单独声明额外的linknode类型的临时变量    linknode **p = &l->head;    while((*p)!=NULL && (*p)->id<id){        p = &(*p)->next;    }    //插入新节点    newNode->next = *p;    *p = newNode;}//删除链表中节点void linklist_del(linklist *l,int id){    //判断链表信息    if(l->head==NULL) return;    //同样采用二级指针遍历链表节点    linknode **p = &l->head;    while((*p)!=NULL && (*p)->id!=id){        p = &(*p)->next;    }    //判断是否为NULL    linknode *del = *p;    if(del==NULL) return;    //释放linknode的data数据    // l->destructor((*p)->data);    //释放linklist中的指向待删除的linknode    free(del);    //重新连接    *p = (*p)->next;}//链表的查找方法void* linklist_find(linklist *l,int id){    //遍历列表的方式二--临时节点变量    //获取头节点    linknode *p = l->head;    while(p!=NULL){        if(p->id==id){            return p->data;        }        p = p->next;    }    return NULL;}//链表的显示void linklist_display(linklist *l){    linknode *p = l->head;    while(p!=NULL){        l->display(p->data);        p = p->next;    }}

值得提出的是,上面在删除和删除节点的代码中遍历链表节点采用的是采用二级指针,linknode **p = &l->head;p是指向linknode的指针的指针,采用指针的好处是不用再声明一个linknode类型的临时变量,而是直接通过指针来实现链表节点的移动从而实现链表的遍历;在链表的显示和查找的代码中,则是通过声明临时linknode临时变量来实现遍历链表的,二级指针在此处的应用值得细细体会。最后,通过实际链表的操作来验证通用链表的效果,以存储结构体类型数据为例,代码如下:

#include<stdio.h>#include<stdlib.h>#include<string.h>#define NAME_LEN (20)//定义结构体类型测试typedef struct{    int no;    char *name;    char sex;    int age;}Student;int get_id_stu(void *p){    Student *stu = (Student *)p;    return stu->no;}void display_stu(void *p){        Student *h = (Student *) p;        printf("%d\t%s\t%c\t%d\n", h->no, h->name, h->sex, h->age);}void destructor_stu(void *p){    Student *stu = (Student *)p;    //释放名字属性,释放引用类型(指针)    free(stu->name);    free(stu);}int main(int argc, char const *argv[]){    linklist *link_stu = linklist_init(&get_id_stu,&display_stu,&destructor_stu);    //插入第一个节点    Student *stu_0 = malloc(sizeof(Student));    stu_0->no = 1701;    stu_0->name = malloc(NAME_LEN);    memcpy(stu_0->name,"xwy01",NAME_LEN);    stu_0->sex = 'F';    stu_0->age = 23;    printf("no:%d,name:%s,sex:%c,age:%d\n", stu_0->no,stu_0->name,stu_0->sex,stu_0->age);    linklist_insert(link_stu,stu_0);    //插入第二个节点    Student *stu_1 = malloc(sizeof(Student));    stu_1->no = 1702;    stu_1->name = malloc(NAME_LEN);    memcpy(stu_1->name,"xwy02",NAME_LEN);    stu_1->sex = 'M';    stu_1->age = 24;    linklist_insert(link_stu,stu_1);    linklist_display(link_stu);    return 0;}

暂时验证的比较简单,插入结构体数据节点并输出,结果符合预期:

no:1701,name:xwy01,sex:F,age:231701    xwy01   F   231702    xwy02   M   24[Finished in 0.0s]

编程外星人