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

7.2 KiB
Raw Permalink Blame History

介绍

双端队列,是有两个出入口,兼备队列和堆栈的特性,两个端口都可以进出数据。
varch双端队列、队列、堆栈的实现原理都是一样的都是连续的地址空间环形连接起来高效的在两端出入数据并且支持随机访问。

  • 容量
    容量也就是在使用过程中最多能存多少个队列项比如容量为10的队列最多能存10个队列项存满后再想入队就入不了。varch的队列存储是连续地址的是有限容量的队列。
  • 访问机制
    一般情况下,队列只有出队和入队两种方式,可以根据连续地址快速的对队列进行遍历访问。

接口

创建和删除deque对象

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对象。创建方法和删除应该成对使用创建出来在结束使用应该删除掉。

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

deque的入队和出队

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。

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的大小、容量和数据大小

int deque_size(deque_t deque);
int deque_capacity(deque_t deque);
int deque_dsize(deque_t deque);

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

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数据的读写

void* deque_data(deque_t deque, int index);
#define deque_at(deque, type, i)

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

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数据存储索引

int deque_index(deque_t deque, int index);

这个队列存储结构为环形队列,也就是在连续地址的存储空间首尾相接形成环形,队列数据进出就在这个环形中进行。如此,队列的索引并不直接是缓冲区的索引,deque_index方法就是将队列的索引对照为缓冲区的索引,失败返回-1。
一般情况下,这个方法应用不多,也是在deque_create方法是传入了base,在base地址上来使用deque_index获取队列数据。

deque空队和满队

int deque_empty(deque_t deque);
int deque_full(deque_t deque);

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

源码解析

deque结构体

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

typedef struct DEQUE *deque_t;

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

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方法时候传进来sizedeque的大小也就是deque的长度dsize(每个数据的大小),capacity(队列的容量),headtail分别是环形缓冲区,队头和队尾所指向的索引。

deque容器最主要的问题就是解决环形队列数据先进先出的问题其他创建删除是完成空间的初始化等基本初始化操作deque_push_backdeque_pop_front其实与queue是一致的这里不在赘述主要说明下deque_push_frontdeque_pop_back

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为0push 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;
}