
前言
熟悉C++的STL(标准模板库)的人都知道list底层是一个双向链表,支持通用类型数据的存储,使用起来非常方便,但对于C语言开发者来说,并没有这么方便的工具,所以在这里记录一下自己实现的通用数据类型双向链表,提供了一些常见的API,并分享一些常见的代码规范。
设计思路
这里我们不便过多地讨论双向链表的实现过程,我们重点看如何解决下面几个问题
1. 如何解决通用类型的数据存储
2. 如何保证代码的严谨性,健壮性
3. 如何实现数据结构的高复用,低耦合
1.通过 void 指针解决通用数据类型存储
思路:当链表节点的Data域是 void * 类型时才能够接收任意类型数据的指针,在插入链表时传入数据的地址,用户在使用时自己转换成原来数据类型的指针再使用即可,所以双向链表可以存 int ,double,甚至是结构体,链表类型的数据,完美的解决了通用数据类型存储的问题,链表结构体定义如下。
/* 通用数据类型,双向链表节点 */
typedef struct DNode
{
struct DNode* prior; /* 链表节点的前驱 */
void* data; /* 链表节点的data域 */
struct DNode* next; /* 链表节点的后继 */
}DNode;
2.如何保证代码的严谨性,健壮性
很多人写代码不讲究代码的严谨性,总是匆匆的完成了功能就接手下一个,造成的结果往往是后期要投入大量的时间进行维护,所以最好做到以下几点。
1.传入指针避免使用assert放错,一旦错误程序立马终止,不利于错误检查
2.malloc申请内存空间之后一定要检查,防止出现malloc失败导致的后果
3.程序结束之后需要进行内存释放,并用专业内存检查工具检查
4.接口调用失败返回错误码,以便查看错误原因
5.外部不需要调用的接口使用 static 声明,防止用户调用
2.如何实现数据结构的高复用,低耦合
链表内部不提供打印函数,只提供遍历函数DListForeach,根据传入的回调函数来操作节点。
采用传入回调函数的方式,处理不同数据类型节点操作,节点空间释放,节点大小比较,将其函数指针作为接口参数传入,在链表内部进行自定义的操作,这是一个完美的解决方案,不太清楚函数指针的同学可以下去补补课,详细实现见代码。
这里为了通用性和便捷性,模拟了list的做法,给链表接口传入链表指针而不是头节点,所以定义了链表结构体。
/* 表示整个链表 */
typedef struct DList
{
DNode* head;
int listlength;
}DList;
代码展示
GenDList.h
#ifndef _DLIST_H_
#define _DLIST_H_
#include <stdbool.h>
// 函数的返回码
typedef enum DListRet
{
DLIST_RET_OK, // 操作调用成功
DLIST_RET_FAIL, // 操作调用失败
DLIST_IS_NULL // 链表为空
}DListRet;
/* 通用数据类型,双向链表节点 */
typedef struct DNode
{
struct DNode* prior; /* 链表节点的前驱 */
void* data; /* 链表节点的data域 */
struct DNode* next; /* 链表节点的后继 */
}DNode;
/* 链表节点操作回调函数 */
typedef void (*DListNodeOperation)(void* outdata,
void* intputData);
/* 销毁链表节点data域回调函数 */
typedef void (*DataDestory)(void* pDList, void* DNode);
/* 节点数据查找回调 */
typedef int (*DListDataFind)(void* content,void* data);
/* 节点数据比较回调 */
typedef int (*DListDataCompare)(void* ctx, void* data);
/* 表示整个链表 */
typedef struct DList
{
DNode* head;
int listlength;
}DList;
DList* DListCreat();
int DListLength(DList* pDList);
static DNode* CreateDNode(DList* pDist, void* data);
static DNode* GetPosNode(DList* pDList, int index);
DListRet InsertPos(DList* pDList, int index, void* data);
DListRet InsertBack(DList* pDList, void* data);
DListRet InsertFront(DList* pDList, void* data);
DListRet DListForeach(DList* pDist, DListNodeOperation nodeOp,
void* outdata);
DListRet DeletePos(DList* pDList, int index,DataDestory datafreefun);
bool IsEmpty(DList* pDList);
void DlistDestory(DList* pDList,DataDestory datafreefun