mirror of
https://gitee.com/Lamdonn/varch.git
synced 2025-12-06 16:56:42 +08:00
187 lines
7.2 KiB
Markdown
187 lines
7.2 KiB
Markdown
## 介绍
|
||
|
||
双端队列,是有两个出入口,兼备队列和堆栈的特性,两个端口都可以进出数据。
|
||
varch双端队列、队列、堆栈的实现原理都是一样的,都是连续的地址空间,环形连接起来,高效的在两端出入数据,并且支持随机访问。
|
||
* 容量
|
||
容量也就是在使用过程中最多能存多少个队列项,比如,容量为10的队列,最多能存10个队列项,存满后,再想入队就入不了。varch的队列存储是连续地址的,是有限容量的队列。
|
||
* 访问机制
|
||
一般情况下,队列只有出队和入队两种方式,可以根据连续地址快速的对队列进行遍历访问。
|
||
|
||
## 接口
|
||
|
||
### 创建和删除deque对象
|
||
```c
|
||
deque_t deque_create(int dsize, int capacity, void *base);
|
||
void deque_delete(deque_t deque);
|
||
#define deque(type, capacity) // 为了更简便的使用,对deque_create套一层宏定义
|
||
#define _deque(deque) // 对deque_delete套一层宏定义,并在deque删除后置为空
|
||
```
|
||
其中**deque_t**为deque的结构体,创建方法则会返回一个deque对象,创建失败则返回NULL,其中`dsize`传入数据的大小,`capacity`传入队列容量,`*base`传入缓冲区地址(可以不传入,不传入的话就会自动分配capacity大小的空间以来存储队列数据)。删除方法则是删除传入的deque对象。创建方法和删除应该成对使用,创建出来在结束使用应该删除掉。
|
||
```c
|
||
void test(void)
|
||
{
|
||
deque_t deque = deque(int, 10); // 定义并创建一个int型容量为10的deque
|
||
_deque(deque); // 成对使用,用完即删除
|
||
}
|
||
```
|
||
|
||
### deque的入队和出队
|
||
```c
|
||
int deque_push_front(deque_t deque, void* data);
|
||
int deque_push_back(deque_t deque, void* data);
|
||
int deque_pop_front(deque_t deque, void* data);
|
||
int deque_pop_back(deque_t deque, void* data);
|
||
```
|
||
这四个方法可以很方便的把数据添加到队列和从队列弹出,`push`方法`data`传入需要入队数据的地址,`pop`方法`data`传入需要接收出队数据的地址,这两个方法`data`都可以传入NULL,那只是一个占位。操作成功返回1,失败返回0。
|
||
```c
|
||
void test(void)
|
||
{
|
||
deque_t deque = deque(int, 10);
|
||
int i = 0;
|
||
|
||
for (i = 0; i < deque_capacity(deque); i++)
|
||
{
|
||
deque_push_back(deque, &i);
|
||
}
|
||
deque_pop_front(deque, NULL);
|
||
deque_pop_back(deque, NULL);
|
||
|
||
_deque(deque); // 成对使用,用完即删除
|
||
}
|
||
```
|
||
|
||
|
||
### deque的大小、容量和数据大小
|
||
```c
|
||
int deque_size(deque_t deque);
|
||
int deque_capacity(deque_t deque);
|
||
int deque_dsize(deque_t deque);
|
||
```
|
||
deque的`capacity`就是创建时候指定的容量,能存多少个队列元素,`size`是队列有多少个元素,`dsize`也就是创建时候传入的数据的大小,比如`int`,`dsize`就是`sizeof(int)`。
|
||
```c
|
||
void test(void)
|
||
{
|
||
deque_t deque = deque(int, 10);
|
||
int i = 0;
|
||
|
||
for (i = 0; i < deque_capacity(deque); i++)
|
||
{
|
||
deque_push_back(deque, &i);
|
||
}
|
||
deque_pop_front(deque, NULL);
|
||
deque_pop_back(deque, NULL);
|
||
printf("deque capacity=%d, size=%d, dsize=%d\r\n", deque_capacity(deque), deque_size(deque), deque_dsize(deque));
|
||
|
||
_deque(deque);
|
||
}
|
||
```
|
||
结果:
|
||
```
|
||
deque capacity=10, size=8, dsize=4
|
||
```
|
||
|
||
### deque数据的读写
|
||
```c
|
||
void* deque_data(deque_t deque, int index);
|
||
#define deque_at(deque, type, i)
|
||
```
|
||
`deque_data`方法就是根据索引来获取数据的地址,返回的则是指定的数据的地址,NULL则是失败。而`deque_at`则是在`deque_data`的基础上加多类型。
|
||
|
||
```c
|
||
void test(void)
|
||
{
|
||
deque_t deque = deque(int, 10);
|
||
int i = 0;
|
||
|
||
for (i = 0; i < deque_capacity(deque); i++)
|
||
{
|
||
deque_push_back(deque, &i);
|
||
}
|
||
deque_pop_front(deque, NULL);
|
||
deque_pop_back(deque, NULL);
|
||
for (i = 0; i < deque_size(deque); i++)
|
||
{
|
||
printf("deque[%d] = %d\r\n", i, deque_at(deque, int, i));
|
||
}
|
||
|
||
_deque(deque);
|
||
}
|
||
```
|
||
结果:
|
||
```
|
||
deque[0] = 1
|
||
deque[1] = 2
|
||
deque[2] = 3
|
||
deque[3] = 4
|
||
deque[4] = 5
|
||
deque[5] = 6
|
||
deque[6] = 7
|
||
deque[7] = 8
|
||
```
|
||
|
||
### deque数据存储索引
|
||
|
||
```c
|
||
int deque_index(deque_t deque, int index);
|
||
```
|
||
这个队列存储结构为环形队列,也就是在连续地址的存储空间首尾相接形成环形,队列数据进出就在这个环形中进行。如此,队列的索引并不直接是缓冲区的索引,`deque_index`方法就是将队列的索引对照为缓冲区的索引,失败返回-1。
|
||
一般情况下,这个方法应用不多,也是在`deque_create`方法是传入了`base`,在`base`地址上来使用`deque_index`获取队列数据。
|
||
|
||
### deque空队和满队
|
||
```c
|
||
int deque_empty(deque_t deque);
|
||
int deque_full(deque_t deque);
|
||
```
|
||
这两个方法实际就是deque的`size`的大小关系,等于0为空,等于容量则满。
|
||
|
||
## 源码解析
|
||
|
||
### deque结构体
|
||
|
||
deque容器的所有结构体都是隐式的,也就是不能直接访问到结构体成员的,这样子的方式保证了模块的独立与安全,防止外部调用修改结构体的成员导致deque存储结构的破坏。所以deque解析器只留了唯一一个deque的声明在头文件,然后结构体的定义都在源文件。只能使用deque容器提供的方法对deque对象进行操作。
|
||
deque类型声明
|
||
```c
|
||
typedef struct DEQUE *deque_t;
|
||
```
|
||
使用时候,只是用`deque_t`即可。
|
||
|
||
```c
|
||
typedef struct DEQUE
|
||
{
|
||
void* base; /* base address of data */
|
||
int cst; /* base const */
|
||
int dsize; /* size of deque data */
|
||
int capacity; /* capacity of deque */
|
||
int size; /* size of deque */
|
||
int head; /* index of deque head */
|
||
int tail; /* index of deque tail */
|
||
} DEQUE;
|
||
```
|
||
`QUEUE`结构体中包含了7个成员,`base`(队列结构数据缓冲区的基地址),`cst`(指示base的空间是否是create方法时候传进来),`size`(deque的大小,也就是deque的长度),`dsize`(每个数据的大小),`capacity`(队列的容量),`head`和`tail`分别是环形缓冲区,队头和队尾所指向的索引。
|
||
|
||
deque容器最主要的问题就是解决环形队列,数据先进先出的问题,其他创建删除是完成空间的初始化等基本初始化操作,`deque_push_back`和`deque_pop_front`其实与queue是一致的,这里不在赘述,主要说明下`deque_push_front`和`deque_pop_back`。
|
||
|
||
```c
|
||
int deque_push_front(deque_t deque, void* data)
|
||
{
|
||
if (!deque) return 0;
|
||
if (deque_full(deque)) return 0; // 在入队之前先判断一下队列是否满了
|
||
// 这里就不是直接head自减就行了,考虑到head为0,自减就是-1了,超出capacity范围了
|
||
// 而我们要的是head为0,push head后,head应该回到缓冲区末端了,保证这个环形的连续
|
||
// 所以这里的做法是,让head+capacity后再来减1,最后对capacity求余保证环形
|
||
deque->head = (deque->head + deque->capacity - 1) % deque->capacity;
|
||
if (data) memcpy(at(deque->head), data, deque->dsize);
|
||
deque->size++;
|
||
return 1;
|
||
}
|
||
int deque_pop_back(deque_t deque, void* data)
|
||
{
|
||
if (!deque) return 0;
|
||
if (deque_empty(deque)) return 0; // 在出队之前先判断一下队列是否为空
|
||
// 这里的tail的操作就如同deque_push_front方法的head操作了
|
||
deque->tail = (deque->tail + deque->capacity - 1) % deque->capacity;
|
||
if (data) memcpy(data, at(deque->tail), deque->dsize);
|
||
deque->size--;
|
||
return 1;
|
||
}
|
||
``` |