C语言宏定义实现常用数据结构——双向循环链表

C语言宏定义实现常用数据结构——双向循环链表


一、背景:普通C语言的链表存在的问题

C语言中没有多态,如果在一个工程中,你想同时使用整形链表,浮点型链表,就要定义两个结构体:List_int和List_float ,然后同样的插入、删除节点的代码要写两遍。

举一个例子:假设你要在一个整型链表的一个节点node后面插入一个整数,这个插入函数的接口可能是下面这样的:

typedef struct Node_
{
    int data;
    struct Node_* next;
}Node;
bool insert_next(Node* node,int data){	... }

假设你要在一个浮点型链表的一个节点node后面插入一个浮点数,这个插入函数的接口可能是下面这样的:

typedef struct Node_
{
    float data;
    struct Node_* next;
}Node;
bool insert_next(Node* node,float data){	... }

在《算法精解C语言描述》里面,是使用void*指针来实现多态的,如下所示

typedef struct Node_
{
    void* data;
    struct Node_* next;
}Node;
bool insert_next(Node* node,void*data){	... }

节点Node只保存数据的指针,通过强制类型转换,data可以是任意数据类型的指针。但这样做有风险,你不知道data的数据到底保存在哪里,如果你一不小心使用类似下面的代码:

typedef struct Node_
{
    void* data;
    struct Node_* next;
}Node;
bool insert_next(Node* node,void*data){	... }
void insert_PI(Node* node)
{
	float pi=3.141592f;
	insert_next(node, &pi);
}

就悲剧了。因为变量(float pi)在函数退出后,就被出栈了。后面可能会被其他函数修改地址(&pi)的值,这时链表里保存的数据就出错了。这时只能使用下面的方式:

typedef struct Node_
{
    void* data;
    struct Node_* next;
}Node;
bool insert_next(Node* node,void*data){	... }
void insert_PI(Node* node)
{
	float* pi=(float*)malloc(sizeof(float));
	* pi=3.141592f;
	insert_next(node, &pi);
}

用malloc函数分配一个内存,这个内存不会放到函数的栈区域。在函数退出之后,(* pi)也不会被其他函数无意修改。

这样的代码虽然可以用,但使用的时候容易出错。

二、前言

为了避免重复劳动,我写了通用的数据结构的库函数。
为了实现多态的特性,在这个库函数中使用了大量的指针强制类型转换。
为了提高运行效率,和避免在一些单片机(如STC系列单片机)中出现函数重入的警告,所有的功能函数都用宏函数的方式来描述。

这篇文章先介绍链表。链表有很多种,单向链表,双向链表,单向循环链表,双向循环链表。为了提高代码的适用范围,这里选择实现双向循环链表。

数据结构的知识建议看《算法精解C语言实现 kyle Loudon著》,这里就不赘述了。


三、内存管理

C语言还有一个很麻烦的事:内存管理。我不喜欢用malloc函数动态分配内存。因为我看不到这个函数的源代码,不放心。而且这个函数效率也不高。

为了提高内存分配的效率,减少内存碎片产生,我们自己设计一个动态内存分配的函数。这里参考了《数据结构、算法与应用C++语言描述Sartaj Sahni著》3.6模拟指针的内容。

#include <iostream>

//为了让这个数据结构可以用在C语言的不同数据类型上,将数据操作都以指针的形式表现出来。
//函数只输入,返回指针。

#define TypeSize_int            4    //定义int占用的数据长度。STC51单片机是2字节,一般的32位机是4字节
#define TypeSize_point          4    //定义指针占用的数据长度。STC51单片机是2字节,32位机是4字节
#define TypeSize_SpaceNode      8    //定义下面数据结构SpaceNode的长度。SpaceNode里面有两个指针型变量,共占8字节。

//空间节点,节点前面几个字节是两个指针,用来保存前后两个节点的地址。
typedef struct SpaceNode_
{
    struct SpaceNode_* prev;
    struct SpaceNode_* next;
}SpaceNode;

//节点的空间,这里的节点都至少包含两个元素:next,prev。
typedef struct NodeSpace_
{
    char* buffer_head;  //内存空间的头地址
    char* buffer_tail;  //内存空间的尾地址

    int nodeSize;       //节点大小。节点的前面是prev和next的指针,后面跟着节点的数据。
    int numberOfNodes;  //内存空间能容纳节点的数量

    //下面维护两个表来记录空白节点
    SpaceNode* first_1;    //表1记录未用过的空白节点头指针。
    SpaceNode* first_2;    //表2记录使用过,但被释放了的空白节点头指针。表2是一个双向链表。
    SpaceNode first_2_tail;  //表2的尾节点。避免first_2是空表时没有元素。
}NodeSpace;

//初始化节点空间。
//dataTypesize:节点的数据类型的尺寸。如:float 4字节,char 1字节。
//char* buf:内存空间的数组。
//int buf_len :buf数组的长度。
void init_NodeSpace(NodeSpace* ns, int dataTypesize, char* buf, int buf_len)
{
    buf = ((int)buf % 4 == 0) ? buf : (buf + 4 - (int)buf % 4);       //为了在32位机上能提高效率,头部向下4字节对齐。
    buf_len = ((int)buf % 4 == 0) ? buf_len : (buf_len - 4 + (int)buf % 4);
    ns->nodeSize = dataTypesize + TypeSize_SpaceNode;
    ns->numberOfNodes = buf_len / ns->nodeSize;

    ns->buffer_head = buf;
    ns->buffer_tail = ns->buffer_head + ns->numberOfNodes * ns->nodeSize;

    ns->first_1 = (SpaceNode*)(ns->buffer_head);  //初始化表1
    ns->first_2 = &(ns->first_2_tail);            //初始化表2
    ns->first_2_tail.prev = NULL;
    ns->first_2_tail.next = NULL;
}

//从(nodeSpace)的first_1里分配一个节点,返回节点的指针。前提是first_1非空
#define allocate_from_first_1(nodeSpace) \
(void*)((nodeSpace).first_1 = (SpaceNode*)((char*)((nodeSpace).first_1) + (nodeSpace).nodeSize), (char*)((nodeSpace).first_1) - (nodeSpace).nodeSize)

//从(nodeSpace)的first_2里分配一个节点,返回节点的指针。前提是first_2非空
#define allocate_from_first_2(nodeSpace) (void*)((nodeSpace).first_2 = (SpaceNode*)(*(nodeSpace).first_2).next, (*(nodeSpace).first_2).prev) 

//(nodeSpace)的表first_1是否是空的
#define is_first_1_empty(nodeSpace)     ((nodeSpace).first_1 == (SpaceNode*)(nodeSpace).buffer_tail)

//(nodeSpace)的表first_2是否是空的
#define is_first_2_empty(nodeSpace)     ((nodeSpace).first_2 == (&(nodeSpace).first_2_tail))

//从(nodeSpace)里分配一个节点,返回节点的指针。  先从表first_2中找,然后从表first_1中找,如果都找不到,就返回null
#define new_node(nodeSpace)  \
(void*)(is_first_2_empty(nodeSpace)  \
 ?  (is_first_1_empty(nodeSpace)     \
    ?  (NULL)  \
    :  allocate_from_first_1(nodeSpace))   \
 :  (allocate_from_first_2(nodeSpace)))  

//Delete的节点指针node必须是由New分配的,不然可能会出错。 将节点插进first_2的双向链表里
#define delete_node(nodeSpace, nodepoint) \
 ((*(SpaceNode*)nodepoint).next = (nodeSpace).first_2, \
(nodeSpace).first_2->prev = (SpaceNode*)nodepoint, \
(nodeSpace).first_2 = (SpaceNode*)nodepoint)




     NodeSpace 测试代码      /
typedef struct MyStruct
{
    void* prev;
    void* next;
    int a;
} st;

void print_NodeSpace(NodeSpace* ns)
{
    std::cout << "输出nodespace \r\n";
    if (ns != NULL) {
        std::cout << "buffer_head地址:" << (void*)ns->buffer_head << "\r\n";
        std::cout << "buffer_tail地址:" << (void*)ns->buffer_tail << "\r\n";

        std::cout << "nodeSize :" << ns->nodeSize << "\r\n";
        std::cout << "numberOfNodes :" << ns->numberOfNodes << "\r\n";

        std::cout << "first_1地址:" << (void*)ns->first_1 << "\r\n";
        std::cout << "first_2地址:" << (void*)ns->first_2 << "\r\n";
    }
}

void print_st(st* s)
{
    std::cout << "地址:";
    if (s != NULL) {
        std::cout << s << "  , 前后指针:" << s->prev << "," << s->next << " 值 " << s->a << "\r\n";
    }
}

void test_NodeSpace()
{
    char u[60];
    NodeSpace ns;
    st* sn;
    st* sn1;
    st* sn2;
    st* sn3;
    st* sn4;
    st* sn5;
    init_NodeSpace(&ns, 4, u, 60);
    sn = (st*)new_node(ns);    sn->a = 11;        print_NodeSpace(&ns);
    sn1 = (st*)new_node(ns);   sn1->a = 12;       print_NodeSpace(&ns);
    sn2 = (st*)new_node(ns);   sn2->a = 13;       print_NodeSpace(&ns);
    delete_node(ns, sn);   
    sn3 = (st*)new_node(ns);   sn3->a = 14;       print_NodeSpace(&ns);
    sn4 = (st*)new_node(ns);   sn4->a = 15;       print_NodeSpace(&ns);
    sn5 = (st*)new_node(ns);                      print_NodeSpace(&ns);
    sn5->a = 16;

    print_st(sn);
    print_st(sn1);
    print_st(sn2);
    print_st(sn3);
    print_st(sn4);
    print_st(sn5);
}


四、双向循环链表

代码如下:

 //双向链表结点
typedef struct DListNode_
{
    struct DListNode_* prev;
    struct DListNode_* next;
//    void* data;
} DListNode;

 //双向链表
typedef struct DList_
{
    NodeSpace* nodeSpace; //节点的空间
    int        size;      //链表元素个数。
    DListNode* head;      //链表的头节点。
    DListNode* current;   //链表的当前节点。插入新节点,或者遍历元素,都用它作为中间变量。
} DList;

//初始化双向链表,NodeSpace是链表节点存储的空间。
void dlist_init(DList* list, NodeSpace* ns)
{
    /// 初始化链表
    list->nodeSpace = ns;
    list->size = 0;
    list->head = NULL;
    list->current = NULL;
}

//下面的所有宏函数,参数node都是指针形式。

//判断元素 node 是否为指定双向链表 list 的头节点
#define dlist_is_head(list,node) ((void*)(node) == (void*)(list).head)     

//从空链表中插入节点,这个节点成为头节点。返回待初始化的新节点指针。
#define dlist_insert_empty(list) \
(void*)((list).head = (DListNode*)new_node(*(list).nodeSpace), \
(list).head == NULL ? NULL : (  \
    (list).head->prev = (list).head, \
    (list).head->next = (list).head, \
    (list).size = 1,\
    (char*)((list).head) ) )

//从node后面插入新节点,返回待初始化的新节点指针。
#define dlist_insert_next(list,node) \
(void*)((list).current = (DListNode*)new_node(*(list).nodeSpace), \
(list).current == NULL ? NULL : (  \
    (list).current->prev = node, \
    (list).current->next = node->next, \
    node->next->prev = (list).current,\
    node->next = (list).current,\
    (list).size++, \
    (char*)((list).current)) )

//从node前面插入新节点,返回待初始化的新节点指针。
#define dlist_insert_prev(list,node) \
(void*)((list).current = (DListNode*)new_node(*(list).nodeSpace), \
(list).current == NULL ? NULL : (  \
    (list).current->prev = node->prev, \
    (list).current->next = node, \
    node->prev->next = (list).current,\
    node->prev = (list).current,\
    (list).size++, \
    (char*)((list).current) ) )


//移除node节点。如果node是头节点,先把头节点移动到下一个节点,再做删除。
#define dlist_remove_node(list,node) \
if((list).size>1){ \
    if (dlist_is_head(list, node)) { \
        (list).head = (list).head->next; \
    } \
    node->prev->next = node->next; \
    node->next->prev = node->prev; \
    delete_node(*(list).nodeSpace,node); \
    (list).size--; \
} \
else if ((list).size == 1) {  \
    delete_node(*(list).nodeSpace, node); \
    (list).size = 0; \
    (list).head = NULL; \
}

//销毁链表,释放内存。
void dlist_destroy(DList* list)
{
    // 删除每一个元素
    while ((*list).size > 0) {
        dlist_remove_node((*list), (*list).head);
    }
}

     DList 测试代码      /
typedef struct dnode
{
    void* prev;
    void* next;
    int a;
} Dnode;  //双向链表节点

void print_DList(DList* dl)
{
    std::cout << "输出DList ,";
    if (dl != NULL && dl->size > 0) {
        std::cout << "头节点  :" << ((Dnode*)dl->head)->a << " , ";
        std::cout << "节点数量:" << dl->size << "\r\n";
        dl->current = dl->head;
        std::cout << "  " << ((Dnode*)dl->current)->a;
        dl->current = dl->current->next;
        while (dl->current != dl->head)
        {
            std::cout << "  " << ((Dnode*)dl->current)->a;
            dl->current = dl->current->next;
        }
        std::cout << "  \r\n";
    }
    else
    {
        std::cout << "  空表  \r\n";
    }
}

void print_Dnode(Dnode* s)
{
    std::cout << "地址:";
    if (s != NULL) {
        std::cout << s << "  , 前后指针:" << s->prev << "," << s->next << " 值 " << s->a << "\r\n";
    }
}


void test_dlist()
{
    const int len = 80;
    char buf[len];
    NodeSpace ns;
    init_NodeSpace(&ns, 4, buf, len);
    DList list;
    dlist_init(&list, &ns);

    ((Dnode*)dlist_insert_empty(list))->a = 5;
    ((Dnode*)dlist_insert_next(list, list.head))->a = 6;
    ((Dnode*)dlist_insert_next(list, list.head))->a = 7;
    print_DList(&list);
    ((Dnode*)dlist_insert_next(list, list.head))->a = 8;
    ((Dnode*)dlist_insert_prev(list, list.head))->a = 3;
    ((Dnode*)dlist_insert_prev(list, list.head))->a = 4;
    print_DList(&list);

    list.current = list.head->prev;
    dlist_remove_node(list, list.current);
    list.current = list.head->next;
    dlist_remove_node(list, list.current);
    list.current = list.head->next;
    dlist_remove_node(list, list.current);
    print_DList(&list);

    ((Dnode*)dlist_insert_next(list, list.head))->a = 8;
    ((Dnode*)dlist_insert_prev(list, list.head))->a = 3;
    ((Dnode*)dlist_insert_prev(list, list.head))->a = 4;
    print_DList(&list);
    
 //   dlist_remove_node(list, list.head);  这样会出错。list.head在宏展开后会转移失败。不能直接用list.head这个变量名。

    std::cout << "  结束\r\n";

}

解析:
1、dlist_insert_next(list,node)返回的是新插入节点的指针,这个新节点从(list).nodeSpace中分配。宏函数dlist_insert_next只初始化新节点的指针。至于节点的数据区域,需要用户自己去初始化。
2、用户在使用这个库函数的时候,要自己定义节点的结构体类型。自己定义的结构体的第一、第二个数据必须是 prev、 next 的指针。后面再根据使用情况定义数据类型。

typedef struct dnode
{
    void* prev;
    void* next;
    int a;
} Dnode;  //双向链表节点

3、上面自己定义的节点数据的长度(上面的例子中,data就是int a)必须和init_NodeSpace函数的dataTypesize一致,不然会出错。

//初始化节点空间。
//dataTypesize:节点的数据类型的尺寸。如:float 4字节,char 1字节。
//char* buf:内存空间的数组。
//int buf_len :buf数组的长度。
void init_NodeSpace(NodeSpace* ns, int dataTypesize, char* buf, int buf_len)

注意事项:

1、在VisualStudio上编译这个代码,注意把解决方案平台从x64换成x86,不然会出错。
因为这个代码是为32位机准备的。如果是64位机,需要自己修改下面两个宏定义

#define TypeSize_point          4    //定义指针占用的数据长度。STC51单片机是2字节,32位机是4字节,64位机是8字节。
#define TypeSize_SpaceNode      8    //定义下面数据结构SpaceNode的长度。SpaceNode里面有两个指针型。如果是32位机,就是8字节;如果是64位机,就是16字节。

2、如果不是对自己的水平很自信,不要轻易去动上面的宏函数。宏函数的debug也是需要一些小技巧的。

最后,设计了这个代码,我还是挺得意的,嘻嘻。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值