一、前驱知识储备
1.基础知识
在进行队列的实现之前需要建立使用mec.h文件,此文件的作用是产生一个数据类型boolean,boolean类型的结果只有0与1,作为检验函数的执行结果是否正确。代码如下
#ifndef _MEC_H_
#define _MEC_H_
typedef unsigned char boolean;
#define TRUE 1
#define FALSE 0
2.队列的简单介绍
队列是一种线性逻辑结构,也就是一对一结构。队列与堆栈很类似,其差别仅仅在于:堆栈是在同一端进行数据读写(入/出),而队列,入队列在一端,出队列在另一端。
队列的特点:先进先出(FIFO)。队列的表示可以使用链表和数组进行实现,但是二者都存在缺点。使用数组来表示队列时会因为队尾指针指向数组最后一个空间则认为队列满了,但是只有队首指针指向数组的首地址时才是真正的满队列,故用数组这种物理结构表示队列,则,会产生所谓的“假满”现象。而使用链表来进行实现时可以避免假满现象,但是当队列里的数据类型为一个字节时,每个节点会浪费4个字节的空间。
即,用数组用链表(物理非线性结构)和数组(物理线性结构)都可以实现队列;这里使用数组方式。
二、功能分块实现
1.队列的创建与销毁
队列创建
对于队列的创建我们首先需要定义一个控制头,如下所示
typedef struct MEC_QUEUE {
void **queue;
int capacity;
int head;
int tail;
boolean lastAction;
}MEC_QUEUE;
queue是一个指针的指针,指向存放数据的数组;
capacity是一个int类型的数据,表示队列的大小,即能存放的最大数据个数;
head是队列的头指针,tail是队列的尾指针;
lastAction是一个boolean类型的数据,用来存放队列最后一次操作,此目的是为了后文的对队列判空、判满实现做铺垫。
对于队列的创建我们使用了一个返回值为boolean的函数,在函数中首先判断控制块的控制头是否为空,即存在指向控制头的指针;其次判断控制头中是否不为空,最后判断输入的队列中元素个数是否为小于等于0;三者中任一成立均返回FALSE;即:NULL == head || *head != NULL || capacity <= 0。
申请空间:在初始化队列之前我们必须要对队列所需要的数组空间进行申请,使用calloc函数来完成。申请完毕后还需要对申请的空间进行检验是否申请成功,不成功则返回FALSE;
最后是对控制头里的变量进行赋值操作,h作为中间变量传递指针,最终赋值给*head,即指向数组空间的指针。
代码如下:
boolean mecQueueInitializer(MEC_QUEUE **head, int capacity) {
MEC_QUEUE *h = NULL;
if (NULL == head || *head != NULL || capacity <= 0) {
return FALSE;
}
h = (MEC_QUEUE *) calloc(sizeof(MEC_QUEUE), 1);
if (NULL == h) {
return FALSE;
}
h->capacity = capacity;
h->head = h->tail = 0;
h->lastAction = OUT;
h->queue = (void **) calloc(sizeof(void *), capacity);
if (NULL == h->queue) {
free(h);
return FALSE;
}
*head = h;
return TRUE;
}
队列销毁
队列创建完成后我们需要进行销毁操作,由于在创建过程中使用了calloc()函数,故需要销毁所申请的空间,即使用free()函数进行空间的释放。
代码如下:
void mecQueueDestory(MEC_QUEUE **head) {
if (NULL == head || NULL == *head) {
return;
}
free((*head)->queue);
free(*head);
*head = NULL;
}
2.判断队列空与满
判空
判空操作中便使用到了控制头中的lastAction,如果head->head == head->tail,即头与尾指针指向同一空间,并且 OUT == head->lastAction (最后一步执行的为出队列操作),代表队列空。
关于lastAction 需要两个宏定义:#define IN 1
#define OUT 0
代码如下:
boolean isQueueEmpty(const MEC_QUEUE *head) {
if (NULL == head) {
return TRUE;
}
return head->head == head->tail && OUT == head->lastAction;
}
判满
判满与判空的大体都是相同的,差别只有最后一次操作为入队列。
代码如下:
boolean isQueueFull(const MEC_QUEUE *head) {
if (NULL == head) {
return TRUE;
}
return head->head == head->tail && IN == head->lastAction;
}
3. 入队与出队
入队
入队列需要三个步骤,第一步判断数据、头是否为空,队列是否为满队列;第二步把数据的首地址赋值给尾指针指向的数组空间,尾指针加一,由于使用的是循环数组,故加一操作为:head->tail = (head->tail + 1) % head->capacity;第三步把IN 赋值给lastAction。
boolean in(MEC_QUEUE *head, void *data) {
if (NULL == head || NULL == data || isQueueFull(head)) {
return FALSE;
}
head->queue[head->tail] = data;
head->tail = (head->tail + 1) % head->capacity;
lastAction = IN;
return TRUE;
}
出队
出队列与入队列存在一个巨大的差别,入队列只需要返回一个是否成功,而出队列需要返回一个出对列的数据的指针,故返回值为void*,其余类比入队列即可。
代码如下:
void *out(MEC_QUEUE *head) {
void *data = NULL;
if (NULL == head || isQueueEmpty(head)) {
return data;
}
data = head->queue[head->head];
head->head = (head->head + 1) % head->capacity;
head->lastAction = OUT;
return data;
}
4.读取数据
数据的读取操作类比出队列即可,注意形参必须为const(不可更改)。
代码如下:
void *readHead(const MEC_QUEUE *head) {
if (NULL == head || isQueueEmpty(head)) {
return NULL;
}
return head->queue[head->head];
}
总结
以上就是今天要讲的内容,本文仅仅简单介绍了队列的一些实现方法,提供了几个比较实用的队列使用函数。可以在日后需要使用队列完成编码的话可以方便我们快速实现。