mirror of
https://gitee.com/Lamdonn/varch.git
synced 2025-12-06 16:56:42 +08:00
173 lines
8.0 KiB
Markdown
173 lines
8.0 KiB
Markdown
### Introduction
|
|
|
|
A stack is a data structure with a First In Last Out (FILO) characteristic. Generally, it has only one entrance and exit, where data is pushed onto the stack from the top and popped from the top as well. The operations of adding data to the stack is called "push", and removing data from the stack is called "pop".
|
|
The implementation logic of stacks and queues in varch is quite similar. A queue has two ports and stores data cyclically within a specified space, while a stack has only one port. Usually, data enters and exits the stack from the top. The stack bottom data is located in the low-address section of the specified space, and the stack top data is in the high-address section.
|
|
|
|
- **Capacity**: Capacity refers to the maximum number of stack data that can be stored during usage. For example, a stack with a capacity of 10 can store at most 10 stack data items. Once it's full, no more data can be pushed onto it. The stack storage in varch uses continuous addresses and is a stack with a limited capacity.
|
|
|
|
### Interface
|
|
|
|
#### Creation and Deletion of stack Objects
|
|
```c
|
|
stack_t stack_create(int dsize, int capacity, void *base);
|
|
void stack_delete(stack_t stack);
|
|
#define stack(type, capacity) // For more convenient use, a macro definition is wrapped around stack_create
|
|
#define _stack(stack) // A macro definition is wrapped around stack_delete, and the stack is set to NULL after deletion
|
|
```
|
|
Here, **stack_t** is the structure of the stack. The creation method will return a stack object, and it will return NULL if the creation fails. The `dsize` parameter is used to pass in the size of the data, `capacity` is used to pass in the stack capacity, and `*base` is used to pass in the address of the buffer (it can be omitted. If omitted, space with a size of `capacity` will be automatically allocated to store stack data). The deletion method is used to delete the passed-in stack object. The creation method and the deletion method should be used in pairs. Once created, the stack object should be deleted when it's no longer in use.
|
|
```c
|
|
void test(void)
|
|
{
|
|
stack_t stack = stack(int, 10); // Define and create an int-type stack with a capacity of 10
|
|
_stack(stack); // Use them in pairs and delete it after use
|
|
}
|
|
```
|
|
|
|
#### Stack's Pushing and Popping
|
|
```c
|
|
int stack_push(stack_t stack, void* data);
|
|
int stack_pop(stack_t stack, void* data);
|
|
```
|
|
These two methods can conveniently add data to the stack and pop data from the stack. For the `push` method, the `data` parameter is used to pass in the address of the data to be pushed onto the stack. For the `pop` method, the `data` parameter is used to pass in the address that will receive the popped data. For both methods, `data` can be passed as NULL, which just serves as a placeholder. If the operation is successful, 1 is returned; otherwise, 0 is returned.
|
|
```c
|
|
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); // Use them in pairs and delete it after use
|
|
}
|
|
```
|
|
|
|
#### Size, Capacity, and Data Size of the Stack
|
|
```c
|
|
int stack_size(stack_t stack);
|
|
int stack_capacity(stack_t stack);
|
|
int stack_dsize(stack_t stack);
|
|
```
|
|
The `capacity` of the stack is the capacity specified during creation, which indicates how many stack elements can be stored. The `size` represents the number of elements currently in the stack. The `dsize` is the size of the data passed in during creation, for example, for `int`, the `dsize` is `sizeof(int)`.
|
|
```c
|
|
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);
|
|
}
|
|
```
|
|
**Results**:
|
|
```
|
|
stack capacity=10, size=8, dsize=4
|
|
```
|
|
|
|
#### Reading and Writing of Stack Data
|
|
```c
|
|
void* stack_data(stack_t stack, int index);
|
|
#define stack_at(stack, type, i)
|
|
```
|
|
The `stack_data` method is used to obtain the address of the data according to the index and returns the address of the specified data. NULL indicates failure. The `stack_at` method adds the data type on the basis of `stack_data`.
|
|
```c
|
|
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);
|
|
}
|
|
```
|
|
**Results**:
|
|
```
|
|
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 Data Storage Index
|
|
```c
|
|
#define stack_index(stack, index)
|
|
```
|
|
In fact, `stack_index` corresponds to the `index`. If it exceeds the range, -1 is returned to indicate failure. Generally, this method is not widely used. It is mainly used when the `base` address is passed in during the `stack_create` method to obtain stack data based on the `base` address.
|
|
|
|
#### Empty Stack and Full Stack
|
|
```c
|
|
int stack_empty(stack_t stack);
|
|
int stack_full(stack_t stack);
|
|
```
|
|
These two methods are actually related to the `size` of the stack. If the `size` is equal to 0, the stack is empty; if the `size` is equal to the capacity, the stack is full.
|
|
|
|
### Source Code Analysis
|
|
|
|
#### stack Structure
|
|
|
|
All the structures of the stack container are implicit, which means that the members of the structures can't be accessed directly. This way ensures the independence and security of the module and prevents external calls from modifying the members of the structures, which could otherwise damage the storage structure of the stack. So the stack parser only leaves the single declaration of the stack in the head file, and the definitions of the structures are placed in the source file. Only the methods provided by the stack container can be used to operate on stack objects.
|
|
The declaration of the stack type:
|
|
```c
|
|
typedef struct STACK *stack_t;
|
|
```
|
|
When using it, just use `stack_t`.
|
|
```c
|
|
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;
|
|
```
|
|
The `STACK` structure contains 5 members: `base` (the base address of the stack structure's data buffer), `cst` (indicating whether the `base` space is passed in during the `create` method), `dsize` (the size of each data), `capacity` (the capacity of the stack), and `top` (indicating the index of the next stack data, that is, `top` represents the `size`).
|
|
|
|
The main problem that the stack container needs to solve is to implement the First In Last Out characteristic. Other operations like creation and deletion are used to complete basic initialization operations such as space initialization.
|
|
```c
|
|
#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; // Check if the stack is full before pushing data
|
|
if (data) memcpy(at(stack->top), data, stack->dsize); // Copy the data to the stack top
|
|
stack->top++; // Increase the stack top index
|
|
return 1;
|
|
}
|
|
int stack_pop(stack_t stack, void* data)
|
|
{
|
|
if (!stack) return 0;
|
|
if (stack_empty(stack)) return 0; // Check if the stack is empty before popping data
|
|
stack->top--; // Since top points to the index of the next data, it needs to be decreased by 1 first
|
|
if (data) memcpy(data, at(stack->top), stack->dsize); // Copy the data out
|
|
return 1;
|
|
}
|
|
```
|
|
|
|
These functions related to pushing and popping data on the stack implement the core operations of adding and removing elements while maintaining the FILO property of the stack.
|