目录
为什么要写这篇
前段时间实现了链式队列,本来想再写关于环形队列的实现,没想到拖了好些天还没有开始,那就有机会再说吧。提到队列,先入先出型,总会拿后入先出的栈作比较,这次同样用链表来实现。
链栈与链式队列对比
链栈和链式队列在实现上有很大的相似性,都是基于链表实现,主要操作都是两个:对于队列来说是入队和出队,对于栈来说是压栈和出栈。
不过两者终究是不同的,比如说链式队列是尾插法实现,链栈是头插法实现,链式队列需要两个指针,一个队头一个队尾,链栈只需要一个指针,指向栈顶即可。
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;
}