手把手教你c语言队列实现代码,通俗易懂超详细
大家好,我是无际。
近期我们无际单片机编程分享的高级程序架构教程受到了很多粉丝们的高度好评和认可。
这个教程只要用心看的都能体会到里面巨大的价值,特别是工作经验在2-3年的。
我们整个教程里面有一章节是手把手教大家去写一个队列算法。
那今天我把这节课的内容以文章的形式分享出来,方便大家灵活去学习。
一、通过这篇文章你能掌握以下知识:
掌握队列的原理和作用掌握队列的设计思路掌握队列代码编写掌握队列在产品中的应用二、队列的原理和作用
1.队列原理
队列原理其实就像一个管道,如果我们不断往管道里面塞乒乓球,每个乒乓球在管道里就会排成一条队形。
先进去的乒乓球就会先出来,这个就是队列里先进先出的规则。我们看下这个图
球从左边进去,进去的动作叫入列。
然后进去的球在管道里排成一个队列 ,这个叫队列缓存 ,说白了就是数组 ,那么这里存了5个球就相当于是buff[5]。
最右边出来的1号球是最早进去的球,这个出来的动作叫出列 ,所以遵循
了先进先出 的规则。
2.队列作用
队列最主要的作用就是用来管理数据流的,防止数据因为传输频率过快得不到及时处理而丢失 。
比方说串口接收数据,我们一般定义一个数组来存储数据 ,但是数组存储程序写起来没那么方便,需要有一些变量来记录数组当前可存储的下标,移植性各方面性能都比较差。
假如串口数据频率很快,可能这个数组里存储的数据还没处理完,下一组串口数据又过来了,那么这时候数组里的数据就会被新数据覆盖,导致老的数据丢失。
这就是实际产品开发当中会经常碰到的一些痛点。
所以需要一种技术或者算法去解决这个问题,把实现能解决这些痛点的代码更好地封装起来,同时保证很好的移植性和可扩展性、还有灵活性。
这个时候队列算法就派上用场了。
像这种就可以通过队列的方式来处理,每收到一个字节数据都先入列,然后在应用程序同步解析处理,根据队列先进先出的规则,那么老的数据就不会被新的数据“插队”了。
这里只是说出了队列的其中一个应用,实际上队列的作用还有非常多,比如可以用来传递信号,参数等等。
更多的实用性应用可以跟无际单片机编程从实际产品中去学习。
三、队列程序设计思路
其实实现队列的方法有很多种,不同的工程师实现的代码不一样。
但是原理都是一样的,我们要编写代码,首先要很清楚队列的工作原理,这个我们上面已经讲了,那么我们这里来总结队列的3个核心关键点 :
1.队列缓存
2.入列
3.出列
一个队列是不是基本需要这3个必要的操作?
1.队列缓存
那么队列缓存 很好理解,说白了就是直接定义一个数组,数组大小就是队列缓存的大小。
数组越大,队列缓存就越大,能存储的数据就越多,数据传输也越稳定。
入列 就是把1个或若干个数据按顺序存到队列缓存数组里,同样出列 把数据从队列缓存里取出来。
入列和出列的原理懂了,那么我们接下来就要思考一个大家最关心的问题:入列和出列怎么用程序来实现呢?
2.入列
根据我们前面的理论,入列其实就是把数据存进数组的操作,我们平时存数组一般都是buff[0]=1;这样操作。
那么入列其实没那么简单,因为要考虑队列缓存里面当前存了多少个数据的情况。
如果有数据,那么我们就不能从[0]这个下标开始入列,所以我们在入列时要考虑2个问题:
.①队列缓存可以存储的数组下标位置,这个我们一般称为队尾。
②队列是否已满,如果队列缓存满了又有新的数据入列,该怎么处理?这里我们一般处理方式是按照时间顺序,把最早入列的数据丢弃,以新的数据替换。
那么第2个问题呢我们暂时先不管,我们来看下第1个问题。
我们前面的文章学过数组与指针,通过指针的特性,我们在用1个指针变量来代表队尾指针 ,初始化的时候这个队尾指针指向队列缓存数组的首地址。
当入列1个数据时,我的队尾指针就加1,这样是不是就能够知道当前队列缓存的存储可位置地址了?
2.出列
数据入列以后自然要取出来,那么我们取的时候呢也是有原则的,不能乱取,而是从最早入列那个数据的地址开始取。
所以这个出列的数组下标我们称为队头指针 ,同样的我们可以使用指针变量来代表队头。
上图是一个出列的流程,我们这个是满编队的队列。
总共有1,2,3,4,5个数据,那么队头指针指向队列缓存首地址,接着第一个出列的就是数据1。
出列后对头指针加1,就指向了数据2的地址,那么数据2出列后,对头指针又加1,指向数据3的地址,以此类推,这样就能实现先进先出的原则。
三、队列算法代码编写
1.定义队列对象
大家发现了没,不管是入列、还是出列,这些操作都是基于队列这个对象来操作的。
所以,我们先要把队列当做一个对象给定义出来:
通过结构体来封装一个对象再合适不过了。
一个队列的结构体包含3个东西:队头指针、队尾指针、队列缓存 。
当然,这个队列缓存还可以根据你的实际产品应用定义不同的大小。
2.入列算法
入列算法根据前面的理论部分编写,代码经过了批量产品的验证。
这里就不详细解释了,有配套的视频讲解得更详细,要的可以找无际单片机编程获取。
入列和出列之前必须注意要进入临界和退出临界。
进入临界的意思就是把单片机的总中断关闭,退出临界就是恢复进入临界之前的中断状态。
3.出列算法
大家如果仔细看,不管是入列还是出列,都是对结构体的成员进行操作。
所以,c语言玩到后面真的也就是面向对象的编程思维。
拿出列对应的讲解我们也有配套视频,这里就不再重复了,免得大家看得头疼。
4.其他注意
在使用队列前,一定要把队头指针和队尾指针指向队列缓存第一个元素的地址,否则会引起程序崩溃。
四、掌握队列在产品中的应用
1.我现在做串口数据流接收基本都会用
2.传递重要消息(数据)的时候
还有其他的这里就不多说了,等大家学会了以后自然能扩展更多应用。
51单片机串口通信 环形缓冲区队列(FIFO)
最近在做毕业设计刚好涉及到51单片机,简单的研究一下发现51单片机串口只有一个字节的缓存,如果遇到单片机串口中断没有及时处理SBUF的值或者串口中断长时间未退出很容易照成数据丢失,于是就自己写了个缓冲区,代价就是消耗一部分内存空间,时间-空间本来就是一对矛盾体,想减少串口通信中数据丢失问题只能牺牲部分空间,来减少数据通信过程中的丢失问题。
核心代码如下所示:
/**********************************************************/
#define BUFFER_MAX 16 //缓冲区大小
typedef struct _circle_buffer{
unsigned char head_pos; //缓冲区头部位置
unsigned char tail_pos; //缓冲区尾部位置
unsigned char circle_buffer[BUFFER_MAX]; //缓冲区数组
}circle_buffer;
circle_buffer buffer;
void bufferPop(unsigned char* _buf)
{
if(buffer.head_pos==buffer.tail_pos) //如果头尾接触表示缓冲区为空
*_buf=0xFF;
else
{
*_buf=buffer.circle_buffer[buffer.head_pos]; //如果缓冲区非空则取头节点值并偏移头节点
if(++buffer.head_pos>=BUFFER_MAX)
buffer.head_pos=0;
}
}
void bufferPush(const unsigned char _buf)
{
buffer.circle_buffer[buffer.tail_pos]=_buf; //从尾部追加
if(++buffer.tail_pos>=BUFFER_MAX) //尾节点偏移
buffer.tail_pos=0; //大于数组最大长度 制零 形成环形队列
if(buffer.tail_pos==buffer.head_pos) //如果尾部节点追到头部节点 则修改头节点偏移位置丢弃早期数据
if(++buffer.head_pos>=BUFFER_MAX)
buffer.head_pos=0;
}
考虑到看到此博文的人可能有很多小白并不知道如何使用,在此简单的说一下,假设你已经能进行简单的串口发送接收了,然后串口中断部分可以这样写
void serial1(void) interrupt 4
{
if(RI)
{
bufferPush(SBUF);
RI=0;
}
if(TI)
{
TI=0;
}
}
在主程序中我们只需要调用函数就行了如:
void main()
{
unsigned char dat ;
//读取缓冲区一个字符,如果dat=0xff表示缓冲区为空,所以接收的字符不能有0xff。
bufferPop(&dat);
}
bufferPop函数中没调用一次,便从缓冲区取出一个字符,头部指针就会进行偏移,具体看源码并不是很复杂 只是一个数组类型的环形FIFO缓冲区。
有一点要注意的是,如果缓冲区满的话,后面的数据会覆盖最前面的数据。
你可以把缓冲区设置大些,就可以尽可能的减少数据覆盖问题,但是带来的额外问题就是51或者其他系列的单片机RAM是非常小的,并不像PC中缓冲区动不动就1024KB。所以缓冲区设置多大,根据自己需求调整就行了。
相关问答
请问三菱plc优先程序怎么写,比如点歌机那样点完歌等 队列 唱歌?点歌机并不是plc原理,你想要程序优先执行,不如试试把程序分若干子程序,再合理地调用子程序,这样可以按照你的要求执行相应程序!但是,plc有个弊端,先执行程...
什么是嵌入式系统?嵌入式系统是指由硬件和软件组成并且能够移植到硬件设备的操作系统。而相比较于普通的PC操作系统,嵌入式操作系统不能存储大容量的内存和程序。所以嵌入式操作系...
一个栈的入栈序列为A B C D E则不可能的输出序列为?A:a,b,c,d,e进,之后依次出栈;B:a,b,c,d,进,d出,e进,e,c,b,a出;D:a进a出,b进b出……e进e出;C:的话dce都好办,之后的ab做不到。这道题就是没告诉你进...在...
串口丢数据概率高吗?串口丢数据概率高。串口接收端丢包严重时,如果不是因为明显错误导致,一般都是由于数据传输速度快,主控处理不过来造成的,因此想办法降低传输速度,就可以降...
在通信系统中,一个简单的通信模型是由哪几部分组成的?各自是做什么的?一般,发射部分由编码单元(单片机或者其他电路)和发射单元(发射模块)组成。线传输模块啊知道说哪种@_@有很多种连接方式,通过串口和SPI都可以连接单片机。和单...
电气自动化技术(智能电网技术)主干课程有哪些 - きょぅぃひ ...电路(分析)、电力系统分析(暂态、稳态)、电机学、高电压、继电保护、模拟电子、数字电子——我只列出了重要的专业课程(按重要行排列),基础课都差不...
计算机科学与技术专业主要课程有哪些? 申请方计算机科学与技术,这是。本科大学的一个综合类的计算机学科。不具备专业性和方向性。只有在大二大三分专业分方向的时候才知道具体核心专业课程是什...
大佬们有谁清楚!!西安AI-500501数据采集仪,数据采集仪专不...[回答]具有全域智能控制、多元化参数设置、远程设置调试、智能自修复、后台智能成像、断点续传等优势特点。支持各种监测设备支持各种传感器如倾角仪、震...
汽车左右转向灯同时亮是什么原因?左右转向灯同时亮代表叫做危险灯,意思是自己的车辆有故障,提醒其它车辆注意。另一种情况是如果说连续几辆车组成一个车队,为了保持车队的连贯性,车队里所有的...
现代交换原理与通信网技术第8次印刷习题答案_作业帮[回答]1.1为什么说交换设备是通信网的重要组成部分?转接交换设备是通信网络的核心,它的基本功能是对连接到交换节点的传输链路上的信号进行汇集、转接和分...