深入理解数据结构(一):队列 及 C代码框架

    “队列”型数据结构是一种非常经典的数据结构,理解这个数据结构,能够更深入的理解一些功能机制,比如消息队列等。队列的思想对于程序逻辑中的应用,还是非常普遍和重要的,最常见的应用就是消息队列,下面就来分析一下队列数据结构的各种概念。

一、“队列”数据结构能做什么?

    队列数据结构,正如字面意思所述,能够对一堆数据,按照先后顺序来管理,本质上是对内存区域的管理,举例来讲,消息队列,假设一个任务(线程)A和任务B之间需要进行数据通信,也就是需要传递数据,对于实时性比较高的系统,可以通过消息邮箱,任务A发送消息到邮箱中,任务B从邮箱中取出消息,这样是一个发,一个收,逻辑也是比较清晰的,但是有一种可能性, 就是任务A 需要不停的给任务B发送消息,但是任务B的执行速度比任务A慢,为了保证不丢数据,这个时候消息队列就能派上用场,任务A将消息发送到一个“队列”型的缓冲区域,每发送一次消息,队列变化一次,而任务B从消息队列中取消息,每取一次消息,队列也变化一次,这样的机制,就能保证消息传输的稳定性,大大降低消息传输失败率了。这也是队列数据结构的一种典型应用。

二、“队列”的基本框架

    前面讲过,“队列”的本质是对于内存的管理,所以 队列的基本框架也是确定的,队列的管理肯定是要设计到如何方便的“入队”和“出队”, 方式方法有很多中,目前通用的方法如下,我们假设内存区域就是一个个的抽屉,一个队列 缓冲区有 10个 抽屉,如下图所示:

                                                                    

上图是一个典型的队列数据结构以及队列管理所需要的简单数据,图中的 变量名解释如下:

   queue:队列名

   start:队列开始标志,也可以认为是开始地址。

   end:队列结束标志,也就是结束地址。

   out:出队索引值,所谓出队,就是可以读取地址。也可以认为是可以出队的数据地址(指针)。

   in:入队索引值, 当然也可以认为是可以插入数据的地址、指针等,一定是最后一个有效元素的下一个空位置。 

   entries:当前队列中,包含的有效数据元素数。

  说明:数据出队的方向是从低地址到高地址,也就是从start向end方向,对应out递增(out++)。,具体到图上,就是从上到下。入队方向同出队方向,

接下来我们就来分步骤的举例,出队和入队的步骤对应图。

1、原始状态,包含6个有效数据条目,也就是entries = 6,start = 0, end = 9(数组的起始地址从0开始)。out = 0, in = 6

2、从队列中取出一个数据元素,取出后, 如下图所示:

     

   此时, entries = 5, out = 1, in = 6

3、接下来,再连续取5次数据,也就是将有效数据全部取出,取出后如下图所示:

   此时out = 6, in = 6, 而entries = 0,也就是说这个队列已经空了,无数据可取了。

4、接下来我们进行一次“入队列”操作,如下图所示:

   此时out=6, in = 7, entries = 1,也就是说可以进行出队 操作了。

5、再进行2次“ 入队操作,队列如下图所示:

  

  此时out=6, in = 9, entries = 3。

6、再进行1次“入队” 操作,这个时候就出现了一个问题,就是 in 何去何从?我们 可能会想到有2种处理方式:

   ① 将这个队列缓冲区域的大小设置的更大一些,但是再大,也有用完的时候,而且太大的空间是很容易造成 内存浪费的。

  ② 给 in 赋值为起始 位置,也就是 start(0),从头来, 毕竟out 之前的数据区域已经被取出,已经空闲了。

  很明显,第②种方案更可取,所以再次“ 入队”操作后,队列如下图:

  此时 in = 0, out = 6, entries = 4.

7、我们再进行6次“入队”操作,将前面空的队列区域填满,此时队列如下图所示:

  此时 in = 6, out = 6, entries = 10,也就是说队列已经满了。

8、假设我们再执行1次“入队”操作,这个时候又带来一个问题,队列中已经没有了可以插入数据元素的位置,因为“出队”操作延迟了,这个时候,我们为了保证数据元素的稳定不丢失,就要禁止“入队”操作了。

9、我们接着进行3次“出队”操作,队列如下所示:

  此时 in = 6, out = 9, entries = 7.

10、如果我们再进行1次“出队”操作,那么就带来一个新的问题,如何刷新 out 值,答案是 给out 赋值 为 start,也就是从头开始。队列如下所示:

  此时 out = 0, in = 6, entries = 6.

11、假设我们再进行“出队”操作,将队列数据元素 取空,如下图所示:

 此时out = 6, in = 6, entries = 0,也就是说队列又一次空了,此时如果我们再进行“出队”操作,将会失败,只能进行“入队”操作。

三、小结

    1、队列是的 操作是“环形”处理方式,不管是“入队”操作还是“出队”操作,都是到end重新来 。

    2、当 in = out时,就代表 着 队列 空了, 我们只能先进行“入队”操作,才能进行“出队”操作。

四、C代码逻辑框架

    这里我们参考嵌入式操作系统ucos的 消息队列 的代码逻辑(只是参考操作逻辑,与 实际的代码结构不一样),如下:

//队列数据结构体
typedef struct os_q
{
	void 	**OSQStart;
	void 	**OSQEnd;
	void 	**OSQIn;
	void 	**OSQOut;
	int 	OSQSize;
	int 	OSQEntries;
} OS_Q;

//创建初始化1个队列

static OS_Q *gpq;  //必须要先声明定义一个队列变量,相当于申请了一段内存区域。
 				  //我们不能对临时变量进行操作及返回

OS_Q *OSQCreate(void **start, int size)
{
	pq->OSQStart = start;
	pq->OSQEnd = &start[size];
	pq->OSQIn = start;
	pq->OSQOut = start;
	pq->OSQSize = size;
	pq->OSQEntries = 0;
}

//向队列中发送消息
char OSQPost(OS_Q *pq, void *pmsg)
{
	//先判断队列是否已经满,如果满,则不能再发送新消息
	if(pq->OSQEntries >= pq->OSQSize){
		return OS_ERR_Q_FULL;    
	}
	//队列不满
	*pq->OSQIn++ = pmsg;    //存入消息,in一定递增
	pq->OSQEntries++;		//有效消息数目递增
	//判断in是否到了队列末尾,如果是,则重新给in赋值start
	if(pq->OSQIn == pq->OSQEnd){
		pq->OSQIn = pq->OSQStart;
	}
	return OS_ERR_NONE;
}


//从队列中取消息,也就是 出队 操作
void *OSQPend(OS_Q *pq)
{
	void *pmsg;
	//先判断队列是否为空,也就是队列有效条目是否为0,如果为空,直接返回
	if(pq->OSQEntries == 0){
		return OS_ERR_Q_NULL;
	}
	//队列不空,则取消息
	pmsg = *pq->OSQOut++;   //取完消息,out一定递增
	pq->OSQEntries--;       //有效数据条目递减
	//判断out是否到了队列末尾,如果是,则重新给out赋值start
	if(pq->OSQOut == pq->OSQEnd){
		pq->OSQOut = pq->OSQStart;
	}
	return pmsg;
}


//程序应用示例代码
#define MY_OSQ_SIZE		100
void *gMsgGrp[MY_OSQ_SIZE];   //消息队列存储地址,最大支持100个


static void *rx_task(void *arg)
{
	char *pmsg;
	while(1){

		//取出消息队列数据
		pmsg = (char *)OSQPend(gpq);
		//取出后的逻辑代码
		//....
		//....
		delay(xxx);
	}
}


static void *post_task(void *arg)
{
	char pmsg[100];    //要发送的消息缓冲区
	while(1){

		//对pmsg进行赋值要发送的消息内容
		sprintf(pmsg, xxxxxxx, xxxx);

		OSQPost(gpq, (void *)pmsg);

		delay(xxx);
	}
}

int main(void *arg)
{
	//初始化消息队列变量
	gpq = OSQCreate(gMsgGrp, MY_OSQ_SIZE);
	//创建任务
	OSTaskCreate(rx_task, xxx,xxxx...);
	OSTaskCreate(post_task, xxx, xxx...);

	while(1){
		//主程序程序逻辑
		.........
		delay(xxx);
	}
}
发布了247 篇原创文章 · 获赞 257 · 访问量 62万+

猜你喜欢

转载自blog.csdn.net/u012351051/article/details/97541173