1、引
- 对于嵌入式编程,经常要用的的外围接口就是串口了,在串口中断中,一般只会收发,而不会做其它的事,收发的数据一般都来自一个队列里。
2、定义
队列一般定义为先进先出的容器,入队时把数据插入到队尾,出队时将数据从队头取出——-FIFO.。
一般队列都有以下的属性和方法:
- front
back
empty
- size
- push_back
- pop_front
3、C语言实现
结构体
#define SIZE 1024
struct Queue
{
int front,back; //头、尾指针
unsigned char data[SIZE]; // 缓冲区大小
bool (*empty)(Queue*); //判空
int (*size)(Queue*); //剩余大小
bool (*push_back)(Queue*,unsigned char); // 入队
bool (*pop_front)(Queue*,unsigned char*);// 出队
};
模块内部的接口定义
static bool empty(Queue* q);
static int size(Queue*q);
static bool push_back(Queue* q,unsigned char d);
static bool pop_front(Queue* q,unsigned char* d);
//对外的初始化接口
void init(Queue* q)
{
q->back = 0 ;
q->front = 0;
q->size = &size;
q->empty = ∅
q->pop_front = &pop_front;
q->push_back = &push_back;
}
//方法的实现
static bool empty(Queue* q)
{
return q->back == q->front;
}
static int size(Queue*q)
{
return (q->back - q->front + SIZE)%SIZE;
}
static bool push_back(Queue* q,unsigned char d)
{
if((q->back+1)%SIZE == q->front)
{
return false;
}
else
{
q->data[q->back] = d;
q->back = (q->back+1)%SIZE;
return true;
}
}
static bool pop_front(Queue* q,unsigned char* d)
{
if(q->empty(q))
{
return false;
}
else
{
*d = q->data[q->front];
q->front = (q->front+1)%SIZE;
return true;
}
}
使用:
Queue queue;
init(&queue);
//入队
for(int i = 0 ; i < 10;i++)
queue.push_back(&queue,i);
//出队
unsigned char d;
bool ret = queue.pop_front(&queue,&d) ;
4、节省空间
这里我们定义了一个长度为SIZE的缓冲区,入队时的判断是如下:
if((q->back+1)%SIZE == q->front)//判满
{
return false;
}
如果定义了SIZE个大小的空间,我们只能使用SIZE-1个空间的大小,使用一个空间来判满。
这对于空间比较充裕的系统来说没有毛病,如果数组的元素是结构体,而且这个结构体还很大时,这就显得相当的浪费了,比如在接收CAN数据帧时LINUX内核里定义了如下结构体:
*/
struct can_frame {
canid_t can_id;/* 32 bit CAN_ID + EFF/RTR/ERR flags */
__u8 can_dlc; /* data length code: 0 .. 8 */
__u8 data[8] __attribute__((aligned(8)));
};
这个结构体是8字节对齐的。
这里为了节省空间可以加多一个标志位:
这个标志位的作用如何运作呢?
初始为:0
如果 (q->back == q->front) && (q->flag == 0); 则是空
如果 (q->back == q->front) && (q->flag == 1); 则是满
入队时:q->back == q->front 刚满了。
出队时:q->back == q->front 刚空了。
需要注意是:满后第一次出队,空时的最后一次出队
5、 Version 2
#define SIZE 5
#define VER2
struct Queue
{
int front,back;
#ifdef VER2
int flag; //新加的标志
#endif
unsigned char data[SIZE];
bool (*empty)(Queue*);
bool (*full)(Queue*);
int (*size)(Queue*);
bool (*push_back)(Queue*,unsigned char);
bool (*pop_front)(Queue*,unsigned char*);
};
初始化:
void init(Queue* q)
{
#ifdef VER2
q->back = 0 ;
q->front = 0;
q->flag = 0; //初始化为0
#else
q->back = 0 ;
q->front = 0;
#endif
q->size = &size;
q->empty = ∅
q->full = &full;
q->pop_front = &pop_front;
q->push_back = &push_back;
}
空与满的判断:
static bool empty(Queue* q)
{
#ifdef VER2
return (q->back == q->front) && (q->flag == 0);//here
#else
return q->back == q->front;
#endif
}
static bool full(Queue* q)
{
#ifdef VER2
return (q->back == q->front) && (q->flag == 1);//here
#else
return (q->back+1)%SIZE == q->front
#endif
}
入队与出队操作:
static bool push_back(Queue* q,unsigned char d)
{
#ifdef VER2
if(q->full(q)) return false;
else
{
q->data[q->back] = d;
q->back = (q->back+1)%SIZE;
if(q->back == q->front && q->flag == 0)//入队时,头尾相接则满了
{
q->flag = 1;
}
return true;
}
#else
if(q->full(q))
{
return false;
}
else
{
q->data[q->back] = d;
q->back = (q->back+1)%SIZE;
return true;
}
#endif
}
static bool pop_front(Queue* q,unsigned char* d)
{
#ifdef VER2
if(q->empty(q))
{
return false;
}
else
{
int tmp = q->front; //这里存一个临时变量
*d = q->data[q->front];
q->front = (q->front+1)%SIZE;
//出最后一个数据的时候要注意,不然头指针会跑到尾指针后面去了。
//也要保证满了后的第一次出队正常
if(tmp == q->back && q->flag == 0)
q->front = q->back;
q->flag = 0;
return true;
}
#else
if(q->empty(q))
{
return false;
}
else
{
*d = q->data[q->front];
q->front = (q->front+1)%SIZE;
return true;
}
#endif
}
6、总结
- 简单,基础
- 尾指针指向下一个空的位置
7、参考资料
https://en.wikipedia.org/wiki/Queue
https://www.tutorialspoint.com/data_structures_algorithms/dsa_queue.htm