实现通用的双向链表(c语言实现)

  国庆8天在家阅读李先静大神的书籍,抽空把一些笔记记录下来。从实现一个通用的双向链表开始。

1. 构建链表节点

  链表节点存值还是存值的地址(指针)?对于通用链表首先要做到能够存放任何数据类型的数据,首先可能会想到的是union,但是数据类型无穷无尽,不可能在union中全部表示。那么可行的办法有两种:
  (1) 存值

typedef struct _DListNode DListNode;
struct _DListNode
{
    DListNode* prev;
    DListNode* next;
    void* data;
    size_t length;
};

  存值即在构建节点时实现深度拷贝源数据,void* data;用于保存拷贝的目的地址,size_t length;用于保存目的地址占据的内存大小。

  (2) 存变量的地址

typedef struct _DListNode DListNode;
struct _DListNode
{
    DListNode* prev;
    DListNode* next;
    void* data;
};

  只保存值的地址,存取效率高,是c语言中常见的做法,在存放整数时可以把void*强制转换整数使用。

  c语言的结构体并没有构造函数,要实现深度拷贝会比较麻烦,所以在c语言中的链表的节点通常是以存指针的方式。

2. 链表打印函数

  对专用双向链表,链表打印函数的实现非常简单,若里面存放的是整数即用%d打印,存放的字符串则用%s打印。但是对于通用的双向链表,程序员无法事先预知里面的数据类型,这就需要到回调函数:程序员无法事先知道链表里面的数据类型,但是使用该链表的用户知道,所以打印函数可以让用户提供。

DListNode* iter = thiz->first;
while (iter != NULL)
{
    print(iter->data);
    iter = iter->next;
}

  显然需要定义一个函数指针:变量指针指向的是一块数据,指针指向不同的变量则可取到不同的数据;函数指针指向的是一个函数,即一段代码,指针指向不同的函数即具有不同的行为。

  定义函数指针类型:

typedef DListRet (*DListDataPrintFunc)(void* data);

  实现链表打印函数dlist_print():

void dlist_print(DList* thiz, DListDataPrintFunc print)
{
    DListNode* iter = thiz->first;

    while (iter != NULL)
    {
        print(iter->data);
        iter = iter->next;
    }
}

  使用方法:

static DListRet print_int(void* data)
{
    printf("%d", (int*)data);
    return DLIST_RET_OK
}
//...
dlist_print(dist, print_int);
3. 链表遍历函数

  实现功能:在一个存放整数的双向链表中找出其中的最大值、累加链表中的所有整数。针对这两个功能初学者一般会编写两个函数:dlist_find_max()和dlist_print(),但是仔细一想就会发现,这两个函数的实现体大部分是跟dlist_print()一样的,即同样的要遍历链表,因此我们可以提供dlist_foreach()函数遍历双向链表,至于遍历过程要做什么,可以通过回调函数让调用者做这些事情。

  回调函数的原型为:

DListRet dlist_foreach(DList* thiz, DListVisitFunc visit, void* ctx)
{
    DListRet ret = DLIST_RET_OK;
    DListNode* iter = thiz->first;

    while (iter != NULL && ret != DLIST_RET_STOP)
    {
        ret = visit(ctx, iter->data);
        iter = iter->next;
    }
    return ret;
}

  dlist_foreach()函数有3个参数,thiz指向该双向链表,DListVisitFunc是一个函数指针,用于遍历链表节点时候的回调,ctx是传给DListVisitFunc的参数。DListVisitFunc的原型为:

typedef DListRet (*DListVisitFunc)(void* ctx, void* data);

  那么累加求和的回调函数为:

static DListRet sum_cb(void* ctx, void* data)
{
    long *ret = ctx;
    *ret += (int)data;

    return DLIST_RET_OK;
}

  调用dlist_foreach():

long sum = 0;
dlist_foreach(thiz, sum_cb, &sum);
4. 实现代码

这里写图片描述

  不论是哪种链表都可以分为有头链表/无头链表,前者是头节点是一个节点变量,后者是头节点是一个头节点指针,也就是它只是占据一个地址大小。在这里的双向链表是无头链表,但是链表的开头是一个DList结构体指针,成员为:

first指针:用于指向链表的真正头节点
data_destroy():销毁链表节点的回调
data_destroy_ctx:销毁回调函数的传参

  具体实现如下:

//dlist.h
#include <stdlib.h>
#ifndef __DLIST_H__
#define __DLIAT_H__

//用c语言的方式编译
#ifdef __cplusplus
extern "C"{
#endif /* _cplusplus */

//枚举,函数的返回值类型
typedef enum _DListRet
{
    DLIST_RET_OK,
    DLIST_RET_OOM,
    DLIST_RET_STOP,
    DLIST_RET_PARAMS,
    DLIST_RET_FAIL
}DListRet;

//DList用于描述整个链表,定义在dlist.cpp中
struct _DList;
typedef struct _DList DList;

//销毁节点的回调
typedef void (*DListDataDestroyFunc)(void* ctx, void* data);
//节点数据比较回调
typedef int (*DListDataCompareFunc)(void* ctx, void* data);
//遍历链表时的回调
typedef DListRet (*DListDataVisitFunc)(void* ctx, void* data);

//可供调用者使用的链表操作函数
DList* dlist_create(DListDataDestroyFunc data_destroy, void* data_destroy_ctx);
DListRet dlist_insert(DList* thiz, size_t index, void* data);
DListRet dlist_prepend(DList* thiz, void* data);
DListRet dlist_append(DList* thiz, void* data);
DListRet dlist_delete(DList* thiz, size_t index);
DListRet dlist_get_by_index(DList* thiz, size_t index, void** data);
DListRet dlist_set_by_index(DList* thiz, size_t index, void* data);
size_t dlist_length(DList* thiz);
int dlist_find(DList* thiz, DListDataCompareFunc cmp, void* ctx);
DListRet dlist_foreach(DList* thiz, DListDataVisitFunc visit, void* ctx);
void dlist_destroy(DList* thiz);

#ifdef __cplusplus
}
#endif /* _cplusplus */

#endif /* __DLIST_H__ */
#include "dlist.h"

//链表节点描述
typedef struct _DListNode
{
    struct _DListNode* pre;
    struct _DListNode* next;

    void* data;
}DListNode;

//链表描述
struct _DList
{
    DListNode* first;
    DListDataDestroyFunc data_destroy;
    void* data_destroy_ctx;
};

/************************** 内部使用的函数 ****************************/
//销毁一个节点的data成员,调用回调函数销毁
static void _dlist_destroy_data(DList* thiz, void* data)
{
    if (thiz->data_destroy != NULL)
    {
        thiz->data_destroy(thiz->data_destroy_ctx, data);
    }

    return;
}

//产生一个节点
static DListNode* _dlist_create_node(DList* thiz, void* data)
{
    DListNode* node = malloc(sizeof(DListNode));

    if (node != NULL)
    {
        node->pre = NULL;
        node->next = NULL;
        node->data = data;
    }
    return node;
}

//通过index获取一个节点
static DListNode* _dlist_get_node(DList* thiz, size_t index, int fail_return_last)
{
    DListNode* iter = thiz->first;

    while (iter != NULL && iter->next != NULL && index > 0)
    {
        iter = iter->next;
        index--;
    }

    if (!fail_return_last)
    {
        iter = index > 0 ? NULL : iter;
    }

    return iter;
}

//销毁一个节点
static void _dlist_destroy_node(DList* thiz, DListNode* node)
{
    if (node != NULL)
    {
        node->next = NULL;
        node->pre = NULL;
        _dlist_destroy_data(thiz, node->data);
        free(node);
    }
    return;
}

/************************** 调用者可使用的函数 ****************************/
//链表生成,参数分别为销毁节点data的回调以及回调的参数
DList* dlist_create(DListDataDestroyFunc data_destroy, void* data_destroy_ctx)
{
    DList* thiz = malloc(sizeof(DList));

    if (thiz != NULL)
    {
        thiz->first = NULL;
        thiz->data_destroy = data_destroy;
        thiz->data_destroy_ctx = data_destroy_ctx;
    }
    return thiz;
}

DListRet dlist_insert(DList* thiz, size_t index, void* data)
{
    DListNode* node = NULL;
    DListNode* cursor = NULL;

    //构造节点
    if ((node = _dlist_create_node(thiz, data)) == NULL)
    {
        return DLIST_RET_OOM;
    }

    //第一个节点
    if (thiz->first == NULL)
    {
        thiz->first = node;

        return DLIST_RET_OK;
    }

    //获取目标节点位置,1表示若超过链表长度length,则插在最后
    cursor = _dlist_get_node(thiz, index, 1);

    //插入链表中间位置
    if (index < dlist_length(thiz))
    {
        //头插
        if (thiz->first == cursor)
        {
            thiz->first = node;
        }
        //插入数据中间
        else
        {
            node->pre = cursor->pre;
            cursor->pre->next = node;

        }
        node->next = cursor;
        cursor->pre = node; 
    }
    //尾插
    else 
    {
        cursor->next = node;
        node->pre = cursor;
    }
}

//头插
DListRet dlist_prepend(DList* thiz, void* data)
{
    return dlist_insert(thiz, 0, data);
}

//尾插
DListRet dlist_append(DList* thiz, void* data)
{
    return dlist_insert(thiz, -1, data);
}

//删除一个节点
DListRet dlist_delete(DList* thiz, size_t index)
{
    //获取目标节点位置,0表示index超过length就返回NULL
    DListNode* cursor = _dlist_get_node(thiz, index, 0);

    if (cursor != NULL)
    {
        if (cursor == thiz->first)
        {
            thiz->first = cursor->next;
        }

        if (cursor->next != NULL)
        {
            cursor->pre->next = cursor->next;
        }

        if (cursor->pre != NULL)
        {
            cursor->next->pre = cursor->pre;    
        }

        _dlist_destroy_node(thiz, cursor);
    }
    return DLIST_RET_OK;
}

//通过index获取一个节点的data
DListRet dlist_get_by_index(DList* thiz, size_t index, void** data)
{
    DListNode *cursor = _dlist_get_node(thiz, index, 0);

    if (cursor != NULL)
    {
        *data = cursor->data;
    }

    return cursor != NULL ? DLIST_RET_OK : DLIST_RET_FAIL;
}

//通过index设置一个节点的data
DListRet dlist_set_by_index(DList* thiz, size_t index, void* data)
{
    DListNode *cursor = _dlist_get_node(thiz, index, 0);

    if (cursor != NULL)
    {
        cursor->data = data;
    }

    return cursor != NULL ? DLIST_RET_OK : DLIST_RET_FAIL;
}

//获取链表的长度
size_t dlist_length(DList* thiz)
{
    size_t length = 0;
    DListNode* iter = thiz->first;

    while (iter != NULL)
    {
        length++;
        iter = iter->next;
    }

    return length;
}

//插摘目标数据所在的节点,比较函数采用回调的方法
int dlist_find(DList* thiz, DListDataCompareFunc cmp, void* ctx)
{
    int i = 0;
    DListNode* iter = thiz->first;

    while (iter != NULL)
    {
        if (cmp(ctx, iter->data) == 0)
        {
            break;
        }
        i++;
        iter = iter->next;
    }

    return i;
}

//遍历链表,并调用回调函数
DListRet dlist_foreach(DList* thiz, DListDataVisitFunc visit, void* ctx)
{
    DListRet ret = DLIST_RET_OK;
    DListNode* iter = thiz->first;

    while (iter != NULL && ret != DLIST_RET_STOP)
    {
        ret = visit(ctx, iter->data);
        iter = iter->next;
    }

    return ret;
}

//销毁整个链表
void dlist_destroy(DList* thiz)
{
    DListNode* iter = thiz->first;
    DListNode* next = NULL;

    while (iter != NULL)
    {
        next = iter->next;
        _dlist_destroy_node(thiz, iter);
        iter = next;
    }
    thiz->first = NULL;
    free(thiz);

    return;
}

  main.c中首先测试链表操作:

//main.c
#include "dlist.h"
#include <stdio.h>

DListRet print(void* ctx, void* data)
{
    printf("%c ", *(int*)data);

    return DLIST_RET_OK;
}

static void dlist_temp(void)
{
    int char_arr[] = {'a', 'b', 'c', 'd', 'e'};
    int i;
    char tmpc = 'A';
    char *tmppc = &tmpc;

    DList* dlist = dlist_create(NULL, NULL);

    for (i = 0; i < 5; i++)
    {
        dlist_append(dlist, &char_arr[i]);
    }

    dlist_foreach(dlist, print, NULL);
    printf("\n");

    dlist_insert(dlist, 0, &tmpc);

    dlist_foreach(dlist, print, NULL);
    printf("\n");

    dlist_prepend(dlist, &tmpc);
    dlist_foreach(dlist, print, NULL);
    printf("\n");

    dlist_append(dlist, &tmpc);
    dlist_foreach(dlist, print, NULL);
    printf("\n");

    dlist_delete(dlist, 2);
    dlist_foreach(dlist, print, NULL);
    printf("\n");

    dlist_get_by_index(dlist, 4, (void**)&tmppc);
    printf("**tmppc = %c\n", *(char*)tmppc);
}

int main(int argc, char* argv[])
{
    dlist_temp();
    return 0;
}

  运行:
这里写图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值