链栈的C语言实现及应用场景---内存池

目录

为什么要写这篇

链栈与链式队列对比

压栈和出栈的具体实现

链栈的应用场景---内存池


为什么要写这篇

前段时间实现了链式队列,本来想再写关于环形队列的实现,没想到拖了好些天还没有开始,那就有机会再说吧。提到队列,先入先出型,总会拿后入先出的栈作比较,这次同样用链表来实现。

链栈与链式队列对比

链栈和链式队列在实现上有很大的相似性,都是基于链表实现,主要操作都是两个:对于队列来说是入队和出队,对于栈来说是压栈和出栈。

不过两者终究是不同的,比如说链式队列是尾插法实现,链栈是头插法实现,链式队列需要两个指针,一个队头一个队尾,链栈只需要一个指针,指向栈顶即可。

typedef int Item;

typedef struct node
{
    Item item;
    struct node* next;
}Node;

typedef struct stack
{
    Node* head;    /* 指向栈首项的指针 */
    int items;      /* 栈中的项数 */
}Stack;

对比一下链式队列的结构定义

typedef int Item;

typedef struct node
{
    Item item;
    struct node* next;
}Node;

typedef struct queue
{
    Node* front;    /* 指向队列首项的指针 */
    Node* rear;     /* 指向队列尾项的指针 */
    int items;      /* 队列中的项数 */
}Queue;

压栈和出栈的具体实现

/**
 * PushStack
 * @brief   从栈尾添加一项
 * @param   item 参数描述: 结构体
 * @param   ps 参数描述: 栈首指针
 * @retval  true 返回值描述: 添加成功
 * @retval  false 返回值描述: 添加失败
 * 
 * @note    如果栈为满,返回false
 * @note    为结点分配内存,如果分配失败,返回
 * @note    将数据域存入结点
 * @note    新结点指向当前栈顶的结点
 * @note    栈顶指针指向新压栈的结点
 * @note    项数加1,返回true
 */
bool 
PushStack(Item item, Stack* ps)
{
    Node* pnew;
    if(StackIsFull(ps))
    {
        return false;
    }

    pnew = (Node*)malloc(sizeof(Node));
    if(pnew == NULL)
    {
        printf("Unable to allocate memory! \n");
        exit(1);
    }

    CopyToNode(item, pnew);

    pnew->next = ps->head;
    ps->head = pnew;

    ps->items++;
    return true;
    
}

/**
 * PopStack
 * @brief   从栈头删除一项
 * @param   pitem 参数描述: 结构体指针
 * @param   ps 参数描述: 栈首指针
 * @retval  true 返回值描述: 删除成功
 * @retval  false 返回值描述: 删除失败
 * 
 * @note    如果栈为空,返回false
 * @note    将要出栈的结点的数据域由pitem指向
 * @note    将指向栈顶的指针指向下一个结点
 * @note    释放要出栈的结点占用的内存
 * @note    项数减1,返回true
 */
bool 
PopStack(Item* pitem, Stack* ps)
{
    Node* pt;
    
    if(StackIsEmpty(ps))
    {
        return false;
    }

    CopyToItem(ps->head, pitem);
    pt = ps->head;
    ps->head = ps->head->next;
    free(pt);
    ps->items--;

    return true;
}

链栈的应用场景---内存池

关于链栈,因为看到了一篇关于内存池的文章,讲单片机没有动态内存管理,随着内存的malloc 和 free,会出现大量的碎片,导致后面申请内存申请不到,程序无法正常运行。那怎么办呢?举个例子,你向朋友借39RMB, 他借给你50块钱,你借51块钱,他借给你100块钱,整借整还,来避免碎片化。对于程序来说内存池:每次申请只能申请固定大小的内存。不是ucos上的那内存池,那个更强大,可以看到已分配的和剩余可用的等等。此处主要是为了了解一下单片机对于内存动态使用的注意事项,以及链栈的某种应用场景。

内存池,分配固定大小的一块内存,当使用时从链栈中申请,就从栈中弹出,就可任意使用了,当使用完毕,释放时,将其再压入栈中。关于用完放回去,再用时原来的数据怎么处理,可以在从栈中弹出时将其清0,这样就不会影响下次使用。

与前面的链栈实现关联性并不是那么大,因为不涉及到内存的分配(malloc)和释放(free),可以说需要重写。为了方便查看,我将其放在了一个文件内,有兴趣的可以看看。

#include <stdio.h>      /* 调用printf 函数 */
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <memory.h>



#define MEM_BUFFER_LEN      5
#define MEM_BUFFER_SIZE     32

#define MAXNODEMUM    MEM_BUFFER_LEN

typedef uint32_t Item;

typedef struct memory
{
    union mem
    {
        Item item;
        uint8_t buffer[MEM_BUFFER_SIZE];      //数组
    }dataDomain;
    struct memory *next;
}mem_t;

static mem_t gmem[MEM_BUFFER_LEN];      //联合体数组

typedef struct stack
{
    mem_t* head;    /* 指向栈首项的指针 */
    uint8_t items;      /* 栈中的项数 */
}Stack;


Stack mem_pool;

/****************************************分界线***************************************/
/**
 * InitializeStack
 * @brief   初始化栈为空
 * @param   ps 参数描述: 栈首指针
 */
void 
InitializeStack(Stack* ps)
{
    ps->head = NULL;
    ps->items = 0;
}

/**
 * StackIsFull
 * @brief   栈是否为满
 * @param   ps 参数描述: 栈首指针
 * @retval  true 返回值描述: 栈为满
 * @retval  false 返回值描述: 栈非满
 */
bool 
StackIsFull(const Stack* ps)
{
    return ps->items == MAXNODEMUM;
}

/**
 * StackIsEmpty
 * @brief   栈是否为空
 * @param   ps 参数描述: 栈首指针
 * @retval  true 返回值描述: 栈为空
 * @retval  false 返回值描述: 栈非空
 */
bool 
StackIsEmpty(const Stack* ps)
{
    return ps->items == 0;
}

/**
 * StackItemCount
 * @brief   查询栈中结点的数目
 * @param   ps 参数描述: 栈首指针
 * @retval  uint8_t 返回值描述: 栈中结点的数目
 */
uint8_t 
StackItemCount(const Stack* ps)
{
    return ps->items;
}


/**
 * PushStack
 * @brief   从栈尾添加一项
 * @param   item 参数描述: 结构体
 * @param   ps 参数描述: 栈首指针
 * @retval  true 返回值描述: 添加成功
 * @retval  false 返回值描述: 添加失败
 * 
 * @note    把新结点指向链表头指向的结点
 * @note    链表头指向新结点
 * @note    链表中项数加1
 */
bool 
PushStack(mem_t* pnode, Stack* ps)
{
    if(StackIsFull(ps))
    {
        return false;
    }

    pnode->next = ps->head;
    ps->head = pnode;

    ps->items++;
    return true;
}

/**
 * PopStack
 * @brief   从栈头删除一项
 * @param   pitem 参数描述: 结构体指针
 * @param   ps 参数描述: 栈首指针
 * @retval  true 返回值描述: 删除成功
 * @retval  false 返回值描述: 删除失败
 * 
 * @note    栈顶的结点指针将用于返回
 * @note    链表头指向下一处
 * @note    要返回的结点指向下一处的指针  为NULL
 * @note    项数减1
 */
bool 
PopStack(mem_t* pnode, Stack* ps)
{
    if(StackIsEmpty(ps))
    {
        return false;
    }

    pnode = ps->head;
    ps->head = ps->head->next;

    pnode->next = NULL;
    ps->items--;

    return true;
}

/****************************************分界线***************************************/

/**
 * mem_pool_init
 * @brief   初始化内存池
 */
void mem_pool_init(void)
{
    for(uint8_t i= 0; i < MEM_BUFFER_LEN; i++)
    {
        PushStack(&gmem[i], &mem_pool);
    }
}

/**
 * mem_pop
 * @brief   分配内存
 * @retval  void* 返回值描述: 空类型指针
 * @note    1.判断链栈是否为空,不为空表示有内存可分配
 * @note    2.将内存从链栈中取出
 * @note    3.将取出的内存清空再交给用户使用
 */
void* mem_pop(void)
{
    mem_t *ret = NULL;
    
    if(PopStack(ret, &mem_pool))
    {
        memset(&(ret->dataDomain.buffer), 0, sizeof(ret->dataDomain.buffer));   ///< 数据空间清0
    }else
    {
        printf("error \n");
    }

    return (void*)ret;
}

/**
 * mem_push
 * @brief   回收内存
 * @param   mem 参数描述: 空类型指针
 */
void mem_push(void* mem)
{
    mem_t* tmp = NULL;

    tmp = (mem_t *)mem;
    PushStack(tmp, &mem_pool);
}

/****************************************分界线***************************************/

int main(void)
{
    mem_t * use_mem;

    mem_pool_init();                    /* 初始化内存池 */
    use_mem = (mem_t *)mem_pop();       /* 从内存池中申请到内存 */
    use_mem->dataDomain.item = 2345;    /* 内存空间使用 */
    mem_push((void*)use_mem);           /* 内存回归内存池 */

    return 0;
}

猜你喜欢

转载自blog.csdn.net/quanquanxiaobu/article/details/113002297