## 介绍 队列是一种先进先出(First In First Out,FIFO)的特殊数据结构,一般情况下它只有一个出口一个入口,从队尾进入从队头出,入队push,出队pop。 * 容量 容量也就是在使用过程中最多能存多少个队列项,比如,容量为10的队列,最多能存10个队列项,存满后,再想入队就入不了。varch的队列存储是连续地址的,是有限容量的队列。 * 访问机制 一般情况下,队列只有出队和入队两种方式,可以根据连续地址快速的对队列进行遍历访问。 ## 接口 ### 创建和删除queue对象 ```c 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对象。创建方法和删除应该成对使用,创建出来在结束使用应该删除掉。 ```c void test(void) { queue_t queue = queue(int, 10); // 定义并创建一个int型容量为10的queue _queue(queue); // 成对使用,用完即删除 } ``` ### queue的入队和出队 ```c int queue_push(queue_t queue, void* data); int queue_pop(queue_t queue, void* data); ``` 这两个方法可以很方便的把数据添加到队列和从队列弹出,`push`方法`data`传入需要入队数据的地址,`pop`方法`data`传入需要接收出队数据的地址,这两个方法`data`都可以传入NULL,那只是一个占位。操作成功返回1,失败返回0。 ```c 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的大小、容量和数据大小 ```c int queue_size(queue_t queue); int queue_capacity(queue_t queue); int queue_dsize(queue_t queue); ``` queue的`capacity`就是创建时候指定的容量,能存多少个队列元素,`size`是队列有多少个元素,`dsize`也就是创建时候传入的数据的大小,比如`int`,`dsize`就是`sizeof(int)`。 ```c 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数据的读写 ```c void* queue_data(queue_t queue, int index); #define queue_at(queue, type, i) ``` `queue_data`方法就是根据索引来获取数据的地址,返回的则是指定的数据的地址,NULL则是失败。而`queue_at`则是在`queue_data`的基础上加多类型。 ```c 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数据存储索引 ```c int queue_index(queue_t queue, int index); ``` 这个队列存储结构为环形队列,也就是在连续地址的存储空间首尾相接形成环形,队列数据进出就在这个环形中进行。如此,队列的索引并不直接是缓冲区的索引,`queue_index`方法就是将队列的索引对照为缓冲区的索引,失败返回-1。 一般情况下,这个方法应用不多,也是在`queue_create`方法是传入了`base`,在`base`地址上来使用`queue_index`获取队列数据。 ### queue空队和满队 ```c int queue_empty(queue_t queue); int queue_full(queue_t queue); ``` 这两个方法实际就是queue的`size`的大小关系,等于0为空,等于容量则满。 ## 源码解析 ### queue结构体 queue容器的所有结构体都是隐式的,也就是不能直接访问到结构体成员的,这样子的方式保证了模块的独立与安全,防止外部调用修改结构体的成员导致queue存储结构的破坏。所以queue解析器只留了唯一一个queue的声明在头文件,然后结构体的定义都在源文件。只能使用queue容器提供的方法对queue对象进行操作。 queue类型声明 ```c typedef struct QUEUE *queue_t; ``` 使用时候,只是用`queue_t`即可。 ```c 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方法时候传进来),`size`(queue的大小,也就是queue的长度),`dsize`(每个数据的大小),`capacity`(队列的容量),`head`和`tail`分别是环形缓冲区,队头和队尾所指向的索引。 queue容器最主要的问题就是解决环形队列,数据先进先出的问题,其他创建删除是完成空间的初始化等基本初始化操作。 ```c #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; } ```