用c语言实现循环队列

        上一篇实现了用链表作为底层结构的FIFO,其实队列只需要进行队首删除和队尾插入操作,如果了解过链表和数组的优缺点就知道,数组更适合快速的访问,而且只要不对数组的中间数据进行插入和删除,那么数组的读写速度要比链表快。那么我们就希望用数组来实现一个FIFO,问题就在于如何用充分利用一个定长数组来实现队列的插入、删除。设想队列的首和尾都只能向一个方向移动,那么队尾指针也指向了数组尾部,就不可以再存数据了,但是此时的数组却是空的。为了实现空间充分利用,就需要让队首和队尾指针循环起来:

(自己画的环形队列的形象表示------)内存里肯定不会有这么奇怪的存储现象,磁盘也不能读取一段二维的存储啊,那么应该这样表示:

差不多就是这个意思。。。

然后关键的让队首队尾指针循环起来,怎么用代码实现:

	//头尾指针循环算法
	inline ring_ptr_t ring_round(ring_ptr_t val, ring_size_t limit)
	{
		return val % limit;
	}

(补充一下,个人认为看懂这个玩意儿不难,自己第一次接触这个循环队列的时候,我立刻想到了补码,各位大佬觉得呢?用补码加法实现源码减法,好神奇的运算过程,溢出和进位到底是怎么一回事,我曾经为一个小学加减法陷入思维泥潭。总之我现在还是想明白了,但是茶壶里的饺子吐不出来,这个循环链表我觉得是和进位与补码运算完全是一个思想。可能我把问题复杂化了,看着玩吧?)

接下来把接口和测试用例一起搬上来,简单的一批,上代码:

myring.h

#include <stdlib.h>
#include <stdbool.h>

#define ROUND(val, limit)				\
    (((limit) & ((limit) - 1)) ?			\
    ((val) % (limit)) : ((val) & ((limit) - 1)))

typedef unsigned short ring_ptr_t;
typedef unsigned short ring_size_t;

typedef struct ring {
	volatile ring_ptr_t head;
	volatile ring_ptr_t tail;
	const ring_size_t size;
	unsigned char * const data;
} ring_t;

#define DECLARE_RING(name, size) \
	ring_t name = {0,0,size,(unsigned char* const)malloc(size*sizeof(unsigned char))};

    //头尾指针循环算法
	inline ring_ptr_t ring_round(ring_ptr_t val, ring_size_t limit)
	{
		return val % limit;
	}

	//返回ring大小
	inline ring_size_t ring_size(ring_t *r)
	{
		return r->size;
	}

	//检查ring是否已满
	inline char ring_is_full(ring_t *r)
	{
		return ring_round(r->head + 1, r->size) == r->tail;
	}

	//检查ring是否为空
	inline char ring_is_empty(ring_t *r)
	{
		return r->head == r->tail;
	}

	//检查ring已使用空间(字节)
	inline ring_size_t ring_data_size(ring_t *r)
	{
		return ring_round(r->size + r->head - r->tail, r->size);
	}

	//检查ring剩余空间(字节)
	inline ring_size_t ring_free_size(ring_t *r)
	{
		return ring_round(r->size + r->tail - r->head - 1, r->size);
	}

    //获取尾部一个字节
	inline unsigned char ring_get(ring_t *r)
	{
		unsigned char data;
		data = r->data[r->tail];
		r->tail = ring_round(r->tail + 1, r->size);
		return data;
	}

	//向头部添加一个字节数据,ring是向头方向增长的
	inline void ring_put(ring_t *r, unsigned char ch)
	{
		r->data[r->head] = ch;
		r->head = ring_round(r->head + 1, r->size);
	}

测试用例:

main.c

int main()
{
#if RING_TEST
	DECLARE_RING(ringA,10);
	cout << dec << ring_is_empty(&ringA) << endl;
	unsigned char data[] = { 0,1,2,3,4,5,6,7,8,9 };
	for (int i = 0; i < sizeof(data)-1;i++) {
		ring_put(&ringA,data[i]);
		cout << "ringPUT" ;
		printf("%d ",data[i]);
		
	}
	cout << endl << "队列已满?" << ring_is_full(&ringA) << endl;
	cout << ring_free_size(&ringA) << endl;
	//这时候队列已经满了,如果继续入队会发生什么事情
	for (int i = 0; i < 3; i++) {
		ring_put(&ringA, i+10);
		cout << "ringPUT" << (i + 10) << " ";
	}
	cout << "打印这个队列" << endl;
	while (!ring_is_empty(&ringA)) {
		cout << "ringPOP" ;
		printf("%d ", ring_get(&ringA));
	}
#endif
	system("pause");
	return 0;
}

测试结果:

这个循环队列实际上是我看到过别的大佬写的之后我搬了一部分接口,就在队列满了之后继续存数据这个操作上,其实我有点不一样的看法。(当然在存数据之前一定要查看队列是不是满了,因为队列一般用在消息流里面的,如果FIFO爆掉了其实你从里面存出来的数据已经没有意义,所以一定要保证队列有剩余空间再存数,或者及时从队列里把数据取出来)。那么回归队满了继续存数,我觉得应该会把队首顶掉,就是队尾存一个队首就会丢失一个,但是上述代码的操作会导致,队列满了之后,就只剩下新存进去的数据。画出来就是这样:

所以说,再看测试用例里面的输出结果,虽然队列溢出了两个数据,但是最终队列里剩下的不是10个数据而仅仅就剩下这两个数据了。不过一处这种情况到底要怎么处理,都看你想要怎么做,总之这是异常情况,只要把不溢出时接口写到位就不会影响通用性与可靠性。(写博客也是记录自己的学习过程,博主菜鸡一个,有问题还请大佬指点)

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值