循环队列C语言面向对象实现

李肖遥 2023-04-03 22:10
    关注、星标公众号,直达精彩内容

来源:https://blog.csdn.net/lin_strong/article/details/88236566



前言

学习完《Test-Driven Development for Embedded C》后对C语言中的面向对象开发又多了一层理解,过两天可能专门出个博客来说说新的理解。

而我已经按照更面向对象的方法大改了原来的那个环形缓冲区模块,考虑到整个结构已经完全不同了,所以直接弃用了原来那个模块,新模块直接重新开始记版本号。

Buffer模块为了通用,定义了前后都可以进出,想当成队列来用比如可以入队用BackIn,出队用FrontOut;相当成栈来用比如可以入栈用BackIn,出栈用BackOut。当然,第三篇中我会给出Queue类,并把Buffer类适配为了Queue类,这样可能用起来更舒服些,虽然当然有额外的开销。

缓冲区介绍

实际项目中我们常常会需要一小块区域来暂存一些数据,可能是用来缓存使用,可能是用来线程间通信,我们把其称为缓冲区/Buffer。这块区域可能是先入先出的(队列)也可能是先入后出的(栈),但反正最后都可以抽象为一个可以在头或尾存取数据的内存区域。

缓冲区的具体实现方式一般有链表和数组,当不能确定需要的缓冲区大小时使用链表较好,能确定时使用数组可以节省很多动态分配内存的开销。

而在具体实现上我们常常会使用环形缓冲区,环形缓冲区就是一个逻辑上环形的区域,因为其(逻辑上)是环形的,所以不需要在内部元素变动的时候需要移动内部剩下的元素。这样就使元素进出头尾的时间复杂度只有O(1),效率十分的高,在通信等领域应用频繁。

模块类图

以下是目前整个模块的类图。

模块设计思路简介

缓冲区是个很常见的需求,即对一块逻辑上环形的区域的头尾进行In和Out操作,以缓存各种类型的数据。对调用者来说,并不需要知道其使用的模块/类内部实际是怎么实现的,只需要知道这个模块/类实现哪几个方法,这几个方法是干什么用的就行(其实就是所谓的面向接口编程)。因此,需要为环形缓冲区定义通用的接口。

在一个略大的项目中我们常常需要在多处使用环形缓冲区。所以在这次的实现中我并没有使用单例的方式来实现这个模块,而是直接默认是多例的方式,不同的实现各自提供Create方法来返回对象引用,Destroy方法来销毁。

实际开发中有时会需要混用多种实现,比如有的你希望使用一个RAM数组,有的想用链表,甚至有的是使用Flash来存储的等。而只要接口相同,调用者不需要知道具体的实现细节,只需要你给他传递一个实现了这个接口的对象就行。这其实就实现了不同模块间的解耦。而为了实现在同一个C工程中调用同一个接口能实际调用不同的实现(即多态),这就需要使用虚表技术。这里就不展开了。

再考虑一个很实际的需求,在CodeWarrior对S12X的编程中,为了节省非分页的RAM,我常想要把这个环形缓冲区放在分页的RAM中,这样,两种环形缓冲区可能唯一的差别就是具体访问某个元素的方式是使用普通指针还是rptr指针,如果分别写一个实现,就会有大量的冗余代码。出于程序员的自我修养,肯定得把这些通用的部分给抽象出来。用继承的方式实现代码的复用。

好像前面说的有点混乱。简而言之,就是按照面向对象的思想,定义不同层次的接口,通过虚表实现多态,通过类继承尽可能复用代码,最终实现这个完整的模块

我们可以照着类图看看目前我的抽象方式。首先所有对象都会有个Destroy方法,所以object接口对其进行了定义。这里我没有专门再定义一个Object虚类,后面可能会抽象出来。而最主要的一个基类叫做Container(容器),所有的容器都要实现getCapacity和getCount接口。而isEmpty和isFull其实是通过调用这两个虚方法返回结果的。

而后虚类BufferTYPE继承了Container虚类,并(虚)实现了环形缓冲区的7个通用方法,包括检查头/尾元素(Front和Back)、从头/尾取出元素(FrontOut和BackOut)、往头/尾放入元素(FrontOut和BackOut)以及清空缓冲区(Cleanup)。

TYPE只是一个代号,使用时要替换成实际类型。目前我实现了UINT8、UINT16、UINT32的。如果需要使用其他类型,如果类型的size为8、16、32,建议直接适配一下就好(其实就是对同样size的进行强制类型转换一下,BufferChar给出了一个示例),这样可以尽可能地实现代码复用,如果是其他特殊的,那就照着源代码中的自己扩展一下吧,主要是C语言没有模板功能,只好直接在名字中标记上类型来区分。

然后继承的BufferTYPEIndexed虚类要实现索引器接口,其是所有能通过索引直接访问的Buffer的基类。

BufferArrayShare类继承BufferTYPEIndexed类并实现了所有通过类数组操作来实现的Buffer类的通用部分,其调用索引器来访问数组元素,这样就实现了不同访问方式的复用。与之相对的则是子类BufferTypeArray和BufferTypeArrayR分别实现了位于直接访问区的数组和分页区数组的索引器。

而末尾的3个实现类则主要负责内存的管理部分,动态分配实例并实现对应的Destroy方法,然后调用父类的Init方法来实现完整的类。项目中可以同时使用这几个类。

当然,因为高度的面向对象,导致有大量的小文件,这其实和面向对象语言提供的各种类有一堆文件一个道理。好在使用起来还是很方便的。

如果只是使用的话建议不需要具体了解内部的实现,根据类图了解下继承关系,看看每个类的接口的描述。然后直接调用实例的方法就行。那一堆小文件就直接全部拖到项目中就好。(PS.没有用到的类是不会链接进去占用内存的,所以直接扔进项目就好了,没用到就没用到。)

现在会有这些文件。


编程约定

接口定义在.h头文件中,如名字中带有Private则代表其中的接口为private或protect的,非开发者不应该使用,否则为public的,供给用户使用。

由于C语言本身没有OO这个概念,所以类的方法以这种方式命名,这样可以很直观地知道是哪个类的方法:

类名_方法();

另,对于多例的类,第一个参数为被调用方法的实例,其后跟其他参数。

如BufferUINT8的Back方法的签名如下:

uint8_t BufferUINT8_Back    (BufferUINT8 buf);
1

子类实例可以直接调用父类方法。如:

uint8_t arr[ARRSIZE];
BufferUINT8ArrayR buf;
buf = BufferUINT8ExternalArray_Create(arr, ARRSIZE);
// 缓冲区前面放入55
BufferUINT8_FrontIn((BufferUINT8)buf,55);
// 现在缓冲区内元素为[ 55 ]

示例程序

这里已隐去不重要的代码:

#include 
#include "BufferExternalArrayR.h"
#include "BufferExternalArray.h"
#include "BufferMallocArray.h"

#pragma push
#pragma DATA_SEG __RPAGE_SEG PAGED_RAM
static uint8_t PagedArray[300];
#pragma pop
static uint8_t nonPagedArray[50];

static void BufferTest(BufferUINT8 buf){
 int i;
 printf("sizeof buffer:%u\nFrontIn: 0 to 9\nBackOut: ",BufferUINT8_getCapacity(buf));
 for(i = 0; i < 10; i++)
   BufferUINT8_FrontIn(buf,i);
 for(i = 0; i < 10; i++)
   printf(" %u",BufferUINT8_BackOut(buf));
 printf("\n");
}
void main(void) {
 BufferUINT8 buf1,buf2,buf3;
 buf1 = BufferUINT8ExternalArrayR_Create(PagedArray,sizeof(PagedArray));
 buf2 = BufferUINT8ExternalArray_Create(nonPagedArray,sizeof(nonPagedArray));
 buf3 = BufferUINT8MallocArray_Create(40);
 printf("buf1(BufferExternalArrayR) Test:\n");
 BufferTest(buf1);
 printf("buf2(BufferExternalArray) Test:\n");
 BufferTest(buf2);
 printf("buf3(BufferMallocArray) Test:\n");
 BufferTest(buf3);
 for(;;) {
}
}

可以看到,上例中BufferTest并不知道传递给他的BufferUINT8的具体实现,它只需要知道这个实例实现了BufferUINT8的方法就可以正确地对其进行操作,从而实现了解耦。

另,上例中的BufferUINT8_getCapacity其实是因为我在Buffer模块的头文件中用宏的方式给Container_getCapacity起了别名,这样用起来就更顺手了对吧。

版权声明:本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。

‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧  END  ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧

关注我的微信公众号,回复“加群”按规则加入技术交流群。


点击“阅读原文”查看更多分享,欢迎点分享、收藏、点赞、在看。

李肖遥 公众号“技术让梦想更伟大”,作者:李肖遥,专注嵌入式,只推荐适合你的博文,干货,技术心得,与君共勉。
评论 (0)
  • 3月27日,长虹中玖闪光超高剂量率电子射线放射治疗系统(e-Flash)临床试验项目在四川大学华西医院正式启动,标志着该项目正式进入临床试验阶段。这不仅是我国医学技术领域的一项重大突破,更是我国在高端医疗设备研发和应用方面的重要里程碑。e-Flash放射治疗系统适用于哪些病症,治疗周期为多久?会不会产生副作用?治疗费用高不高……随着超高剂量率电子射线放射治疗系统(e-Flash)正式进入临床试验阶段,社会各界对该项目的实施情况尤为关注。对此,中国工程院院士范国滨,以及四川大学华西医院、四川省肿瘤
    华尔街科技眼 2025-03-28 20:26 11浏览
  • 本文介绍瑞芯微RK356X系列复用接口配置的方法,基于触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。复用接口介绍由下图可知,红圈内容当前引脚可配置为SPI0或者PWM0功能。由标准系统固件以及相关系统手册可得,当前接口默认配置为SPI0功能:console:/ # ls dev/spidev0.0dev/spidev0.0再由原理图可知当前GPIO为GPIO0_C3
    Industio_触觉智能 2025-03-28 18:14 4浏览
  • 在智能家居领域,无线门铃正朝着高集成度、低功耗、强抗干扰的方向发展。 WTN6040F 和 WT588F02B 两款语音芯片,凭借其 内置EV1527编解码协议 和 免MCU设计 的独特优势,为无线门铃开发提供了革命性解决方案。本文将深入解析这两款芯片的技术特性、应用场景及落地价值。一、无线门铃市场痛点与芯片方案优势1.1 行业核心痛点系统复杂:传统方案需MCU+射频模块+语音芯片组合,BOM成本高功耗瓶颈:待机电流
    广州唯创电子 2025-03-31 09:06 10浏览
  •        随着智能驾驶向L3级及以上迈进,系统对实时性的要求已逼近极限。例如,自动紧急制动(AEB)需在50毫秒内完成感知、决策到执行的全链路响应,多传感器数据同步误差需小于10微秒。然而,传统基于Linux-RT的方案在混合任务处理中存在天然缺陷——其最大中断延迟高达200微秒,且多任务并发时易引发优先级反转问题。据《2024年智能汽车电子架构白皮书》统计,超60%的车企因实时性不足被迫推迟舱驾一体化项目落地。为旌电子给出的破局之道,是采用R5F(实
    中科领创 2025-03-29 11:55 14浏览
  • 本文介绍OpenHarmony5.0 DevEco Studio开发工具安装与配置,鸿蒙北向开发入门必备!鸿蒙北向开发主要侧重于应用层的开发,如APP开发、用户界面设计等,更多地关注用户体验、应用性能优化、上层业务逻辑的实现,需要开发者具备基本的编程知识、对操作系统原理的简单理解,以及一定的UI设计感。由触觉智能Purple Pi OH鸿蒙开发板演示。搭载了瑞芯微RK3566四核处理器,支持开源鸿蒙OpenHarmony3.2至5.0系统,适合鸿蒙开发入门学习。下载与安装开发工具点下面链接下载:
    Industio_触觉智能 2025-03-28 18:16 18浏览
  • 真空容器内部并非wan全没有压强,而是压强极低,接近于零。真空状态下的压强与容器内外气体的分子数量、温度以及容器本身的性质有关。一、真空与压强的基本概念真空指的是一个空间内不存在物质或物质极少的状态,通常用于描述容器或系统中气体的稀薄程度。压强则是单位面积上所受正压力的大小,常用于描述气体、液体等流体对容器壁的作用力。二、真空状态下的压强特点在真空状态下,容器内部的气体分子数量极少,因此它们对容器壁的作用力也相应减小。这导致真空容器内部的压强远低于大气压强,甚至接近于零。然而,由于技术限制和物理
    锦正茂科技 2025-03-29 10:16 10浏览
  • 一、真空容器的定义与工作原理真空容器是一种能够创造并保持一定真空度的密闭容器。其工作原理通常涉及抽气系统,该系统能够逐渐抽出容器内部的气体分子,从而降低容器内的气压,形成真空环境。在这个过程中,容器的体积并不会因抽气而改变,但容器内的压力会随着气体的抽出而逐渐降低。二、真空容器并非恒压系统真空容器并非一个恒压系统。恒压系统指的是在外部环境变化时,系统内部压力能够保持相对稳定。然而,在真空容器中,随着气体的不断抽出,内部压力会持续降低,直至达到所需的真空度。因此,真空容器内部的压力是变化的,而非恒
    锦正茂科技 2025-03-29 10:23 21浏览
  • Shinco音响拆解 一年一次的面包板社区的拆解活动拉开帷幕了。板友们开始大显身手了,拆解各种闲置的宝贝。把各自的设计原理和拆解的感悟一一向电子爱好者展示。产品使用了什么方案,用了什么芯片,能否有更优的方案等等。不仅让拆解的人员了解和深入探索在其中。还可以让网友们学习电子方面的相关知识。今天我也向各位拆解一个产品--- Shinco音响(如下图)。 当产品连接上电脑的耳机孔和USB孔时,它会发出“开机,音频输入模式”的语音播报,。告诉用户它已经进入音响外放模式。3.5mm耳机扣接收电脑音频信号。
    zhusx123 2025-03-30 15:42 24浏览
  • 在智能语音交互设备开发中,系统响应速度直接影响用户体验。WT588F系列语音芯片凭借其灵活的架构设计,在响应效率方面表现出色。本文将深入解析该芯片从接收指令到音频输出的全过程,并揭示不同工作模式下的时间性能差异。一、核心处理流程与时序分解1.1 典型指令执行路径指令接收 → 协议解析 → 存储寻址 → 数据读取 → 数模转换 → 音频输出1.2 关键阶段时间分布(典型值)处理阶段PWM模式耗时DAC模式耗时外挂Flash模式耗时指令解析2-3ms2-3ms3-5ms存储寻址1ms1ms5-10m
    广州唯创电子 2025-03-31 09:26 19浏览
  • 真空容器的材料选择取决于其应用场景(如科研、工业、医疗)、真空等级(低真空、高真空、超高真空)以及环境条件(温度、压力、化学腐蚀等)。以下是常见材料及其优缺点分析:1. 不锈钢(如304、316L)优点:耐腐蚀性强:316L含钼,耐酸碱和高温氧化,适合高真空和腐蚀性环境。高强度:机械性能稳定,可承受高压差和外部冲击。低放气率:经电解抛光或镀镍处理后,表面放气率极低,适合超高真空系统(如粒子加速器、半导体镀膜设备)。易加工:可焊接、铸造,适合复杂结构设计。缺点:重量大:大型容器运输和安装成本高。磁
    锦正茂科技 2025-03-29 10:52 14浏览
  • 文/杜杰编辑/cc孙聪颖‍3月11日,美国总统特朗普,将自费8万美元购买的特斯拉Model S,开进了白宫。特朗普此举,绝非偶然随性,而是有着鲜明的主观意图,处处彰显出一种刻意托举的姿态 。特朗普也毫不讳言,希望他的购买能推动特斯拉的发展。作为全球电动车鼻祖,特斯拉曾凭借创新理念与先进技术,开辟电动汽车新时代,引领行业发展潮流。然而当下,这家行业先驱正深陷困境,面临着前所未有的挑战。就连“钢铁侠”马斯克自己都在采访时表示“非常困难”,的确是需要美国总统伸手拉一把了。马斯克踏入白宫的那一刻,特斯拉
    华尔街科技眼 2025-03-28 20:44 17浏览
  • 在工业控制与数据采集领域,高精度的AD采集和实时显示至关重要。今天,我们就来基于瑞芯微RK3568J + FPGA国产平台深入探讨以下,它是如何实现该功能的。适用开发环境如下:Windows开发环境:Windows 7 64bit、Windows 10 64bitLinux开发环境:Ubuntu18.04.4 64bit、VMware15.5.5U-Boot:U-Boot-2017.09Kernel:Linux-4.19.232、Linux-RT-4.19.232LinuxSDK:LinuxSD
    Tronlong 2025-03-28 10:14 6浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦