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

6.9 KiB
Raw Blame History

介绍

队列是一种先进先出(First In First Out,FIFO)的特殊数据结构一般情况下它只有一个出口一个入口从队尾进入从队头出入队push出队pop。

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

接口

创建和删除queue对象

queue_t queue_create(int dsize, int capacity, void *base);
void queue_delete(queue_t queue);
#define queue(type, capacity) // 为了更简便的使用对queue_create套一层宏定义
#define _queue(queue) // 对queue_delete套一层宏定义并在queue删除后置为空

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

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

queue的入队和出队

int queue_push(queue_t queue, void* data);
int queue_pop(queue_t queue, void* data);

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

void test(void)
{
    queue_t queue = queue(int, 10);
	int i = 0;

	for (i = 0; i < queue_capacity(queue); i++)
	{
		queue_push(queue, &i);
	}
	queue_pop(queue, NULL);
	queue_pop(queue, NULL);

    _queue(queue); // 成对使用,用完即删除
}

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

int queue_size(queue_t queue);
int queue_capacity(queue_t queue);
int queue_dsize(queue_t queue);

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

void test(void)
{
    queue_t queue = queue(int, 10);
	int i = 0;

	for (i = 0; i < queue_capacity(queue); i++)
	{
		queue_push(queue, &i);
	}
	queue_pop(queue, NULL);
	queue_pop(queue, NULL);
	printf("queue capacity=%d, size=%d, dsize=%d\r\n", queue_capacity(queue), queue_size(queue), queue_dsize(queue));

	_queue(queue);
}

结果:

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

queue数据的读写

void* queue_data(queue_t queue, int index);
#define queue_at(queue, type, i)

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

void test(void)
{
    queue_t queue = queue(int, 10);
	int i = 0;

	for (i = 0; i < queue_capacity(queue); i++)
	{
		queue_push(queue, &i);
	}
	queue_pop(queue, NULL);
	queue_pop(queue, NULL);
	for (i = 0; i < queue_size(queue); i++)
	{
		printf("queue[%d] = %d\r\n", i, queue_at(queue, int, i));
	}

	_queue(queue);
}

结果:

queue[0] = 2
queue[1] = 3
queue[2] = 4
queue[3] = 5
queue[4] = 6
queue[5] = 7
queue[6] = 8
queue[7] = 9

queue数据存储索引

int queue_index(queue_t queue, int index);

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

queue空队和满队

int queue_empty(queue_t queue);
int queue_full(queue_t queue);

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

源码解析

queue结构体

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

typedef struct QUEUE *queue_t;

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

typedef struct QUEUE
{
	void* base;						/* base address of data */
	int cst;						/* base const */
	int dsize;						/* size of queue data */
	int capacity;					/* capacity of queue */
	int size;						/* size of queue */
	int head;						/* index of queue head */
	int tail;						/* index of queue tail */
} QUEUE;

QUEUE结构体中包含了7个成员base(队列结构数据缓冲区的基地址),cst指示base的空间是否是create方法时候传进来sizequeue的大小也就是queue的长度dsize(每个数据的大小),capacity(队列的容量),headtail分别是环形缓冲区,队头和队尾所指向的索引。

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

#define at(i) (((unsigned char *)(queue->base))+(i)*(queue->dsize)) /* address of void array */
int queue_push(queue_t queue, void* data)
{
	if (!queue) return 0;
	if (queue_full(queue)) return 0; // 在入队之前先判断一下队列是否满了
	if (data) memcpy(at(queue->tail), data, queue->dsize); // 如果指定了data地址就将data地址的数据复制到尾部tail指向的地址
	queue->tail++; // 让tail+1也就是表明在尾部push了数据
	queue->tail %= queue->capacity; // 让tail对capacity求余保证tail不会超过capacity形成环形
	queue->size++; // size + 1
	return 1;
}
int queue_pop(queue_t queue, void* data)
{
	if (!queue) return 0;
	if (queue_empty(queue)) return 0; // 在出队之前先判断一下队列是否为空
	if (data) memcpy(data, at(queue->head), queue->dsize); // 如果指定了data地址就将队头数据复制到data指向的地址
	queue->head++; // 让head+1也就是表明在头部pop了数据
	queue->head %= queue->capacity; // 让head对capacity求余保证head不会超过capacity形成环形
	queue->size--; // size - 1
	return 1; 
}