varch/doc/stack.md
2024-07-21 19:02:13 +08:00

6.1 KiB
Raw Permalink Blame History

介绍

栈是一种先进后出(First In Last Out,FILO)的数据结构一般情况下只有一个出入口从栈顶进从栈顶出。入栈push出栈pop。
varch的堆栈和队列实现逻辑很类似队列有两个端口循环模式数据在指定空间内循环的存储而堆栈只有一个端口一般情况从栈顶出入栈栈底数据在指定空间的低地址段而栈顶数据在高地址端。

  • 容量
    容量也就是在使用过程中最多能存多少个栈数据比如容量为10的栈最多能存10个栈数据存满后再想入栈就入不了。varch的栈存储是连续地址的是有限容量的栈。

接口

创建和删除stack对象

stack_t stack_create(int dsize, int capacity, void *base);
void stack_delete(stack_t stack);
#define stack(type, capacity) // 为了更简便的使用对stack_create套一层宏定义
#define _stack(stack) // 对stack_delete套一层宏定义并在stack删除后置为空

其中stack_t为stack的结构体创建方法则会返回一个stack对象创建失败则返回NULL其中dsize传入数据的大小,capacity传入栈容量,*base传入缓冲区地址可以不传入不传入的话就会自动分配capacity大小的空间以来存储栈数据。删除方法则是删除传入的stack对象。创建方法和删除应该成对使用创建出来在结束使用应该删除掉。

void test(void)
{
    stack_t stack = stack(int, 10); // 定义并创建一个int型容量为10的stack
    _stack(stack); // 成对使用,用完即删除
}

stack的入栈和出栈

int stack_push(stack_t stack, void* data);
int stack_pop(stack_t stack, void* data);

这两个方法可以很方便的把数据添加到栈和从栈弹出,push方法data传入需要入队数据的地址,pop方法data传入需要接收出队数据的地址,这两个方法data都可以传入NULL那只是一个占位。操作成功返回1失败返回0。

void test(void)
{
    stack_t stack = stack(int, 10);
	int i = 0;

	for (i = 0; i < stack_capacity(stack); i++)
	{
		stack_push(stack, &i);
	}
	stack_pop(stack, NULL);
	stack_pop(stack, NULL);

    _stack(stack); // 成对使用,用完即删除
}

stack的大小、容量和数据大小

int stack_size(stack_t stack);
int stack_capacity(stack_t stack);
int stack_dsize(stack_t stack);

stack的capacity就是创建时候指定的容量,能存多少个栈元素,size是栈有多少个元素,dsize也就是创建时候传入的数据的大小,比如intdsize就是sizeof(int)

void test(void)
{
    stack_t stack = stack(int, 10);
	int i = 0;

	for (i = 0; i < stack_capacity(stack); i++)
	{
		stack_push(stack, &i);
	}
	stack_pop(stack, NULL);
	stack_pop(stack, NULL);
	printf("stack capacity=%d, size=%d, dsize=%d\r\n", stack_capacity(stack), stack_size(stack), stack_dsize(stack));

	_stack(stack);
}

结果:

stack capacity=10, size=8, dsize=4

stack数据的读写

void* stack_data(stack_t stack, int index);
#define stack_at(stack, type, i)

stack_data方法就是根据索引来获取数据的地址返回的则是指定的数据的地址NULL则是失败。而stack_at则是在stack_data的基础上加多类型。

void test(void)
{
    stack_t stack = stack(int, 10);
	int i = 0;

	for (i = 0; i < stack_capacity(stack); i++)
	{
		stack_push(stack, &i);
	}
	stack_pop(stack, NULL);
	stack_pop(stack, NULL);
	for (i = 0; i < stack_size(stack); i++)
	{
		printf("stack[%d] = %d\r\n", i, stack_at(stack, int, i));
	}

	_stack(stack);
}

结果:

stack[0] = 0
stack[1] = 1
stack[2] = 2
stack[3] = 3
stack[4] = 4
stack[5] = 5
stack[6] = 6
stack[7] = 7

stack数据存储索引

#define stack_index(stack, index)

其实stack_index实际就是与index相对应,超出范围失败返回-1。
一般情况下,这个方法应用不多,也是在stack_create方法是传入了base,在base地址上来使用stack_index获取栈数据。

stack空栈和满栈

int stack_empty(stack_t stack);
int stack_full(stack_t stack);

这两个方法实际就是stack的size的大小关系等于0为空等于容量则满。

源码解析

stack结构体

stack容器的所有结构体都是隐式的也就是不能直接访问到结构体成员的这样子的方式保证了模块的独立与安全防止外部调用修改结构体的成员导致stack存储结构的破坏。所以stack解析器只留了唯一一个stack的声明在头文件然后结构体的定义都在源文件。只能使用stack容器提供的方法对stack对象进行操作。
stack类型声明

typedef struct STACK *stack_t;

使用时候,只是用stack_t即可。

typedef struct STACK
{
	void* base;						/* base address of data */
	int cst;						/* base const */
	int dsize;						/* size of stack data */
	int capacity;					/* capacity of stack */
	int top;						/* index of stack top */
} STACK;

STACK结构体中包含了5个成员base(栈结构数据缓冲区的基地址),cst指示base的空间是否是create方法时候传进来dsize(每个数据的大小),capacity(栈的容量),top(指示下一个栈数据索引,也就是top就表示size)。

stack容器最主要的问题就是解决数据先进后出的问题其他创建删除是完成空间的初始化等基本初始化操作。

#define at(i) (((unsigned char *)(stack->base))+(i)*(stack->dsize)) /* address of void array */
int stack_push(stack_t stack, void* data)
{
	if (!stack) return 0;
	if (stack_full(stack)) return 0; // 入栈之前先判断栈是否满了
	if (data) memcpy(at(stack->top), data, stack->dsize); // 将数据复制到栈顶
	stack->top++; // 栈顶增加
	return 1;
}
int stack_pop(stack_t stack, void* data)
{
	if (!stack) return 0;
	if (stack_empty(stack)) return 0; // 出栈之前先判断栈是否为空
	stack->top--; // 因为top指向下一个数据的索引所以这里要先减1了
	if (data) memcpy(data, at(stack->top), stack->dsize); // 再将数据复制出去
	return 1;
}