CoreObjects/GoliahCore17:纯 C 语言中的高效 OOP || 2






3.86/5 (3投票s)
在纯粹、简洁、高效、纯净的 ANSI C 中实现具有 OOP 概念的 Canvas
引言
在本文中,我将着重展示如何在纯粹的 C 语言中轻松采用 OOP 编码。为此,我从头开始编写了一个简单的类库,用于渲染一些几何形状。
背景
这应被视为《CoreObjects/GoliahCore17:纯 C 语言中的高效 OOP》的自然延续。其中介绍的概念假定您已知晓,但继续阅读并不需要。
路由
本文将以引导式的方式,介绍随附示例中的关键点。
我实现了一个图形环境 (Canvas
),能够收集和渲染一些基本的几何形状。
形状 (也称为 Entity
) 的集合实现细节对用户来说是完全隐藏的。因此,只暴露了用于基本插入、删除和向前遍历的 抽象接口
。
为了允许在任何设备上进行渲染,渲染过程是将所有形状提交给一个渲染接口 (CanvasRenderer
),该接口在设备本身之外实现。
目标
示例的目标是实例化一个 Canvas
,添加一些形状,然后使用实现 CanvasRenderer
接口
的代理对象,在任意渲染设备上渲染这些形状。
main
的内容应该大致如下所示:
struct CanvasEntity* ent;
struct CanvasRenderer* renderer = (struct CanvasRenderer*)ConsoleRenderer_build(rendererRegion);
struct Canvas* canvas = Canvas_build(0);
ent = (struct CanvasEntity*)CanvasPoint_build(0,10,11,ColorModelARGB32_make(12,13,14,15));
canvas->invoke->Entities->insert(canvas, ent);
ent = (struct CanvasEntity*)CanvasRectangle_build(0,10,11,12,13,14,ColorModelARGB32_make(15,16,17,18));
canvas->invoke->Entities->insert(canvas, ent);
ent = (struct CanvasEntity*)CanvasCircle_build(0,10,11,12,ColorModelARGB32_make(15,16,17,18));
canvas->invoke->Entities->insert(canvas, ent);
canvas->invoke->Canvas->render(canvas, renderer);
CoreObject_free(canvas);
Canvas 类概览
首先,概览一下层次结构。
Canvas 形状
形状的层次结构基于 抽象
的 CanvasEntity
类。插入了一些其他 抽象
类,为子类提供额外的行为。
实体
CanvasEntity
是形状层次结构的基础类。它提供了一些公共属性,并将操作定义为纯方法。
属性收集在 CanvasEntityAttributes
Attributes 成员中。
Visible
Selected
StrokeWidth
StrokeColor
操作由 CanvasEntityProtocol
定义。
render
getBoundingBo
翻译
rotate
scale
EntityLocated
CanvasEntityLocated
在 GeometricsLocation2D
Location 成员中添加了二维位置信息。
Point 和 Circle
CanvasPoint
和 CanvasCircle
是基于 CanvasEntityLocated
的具体类。
CanvasCircle
在 GeometricsCircleExtent Extent
成员中添加了 radius
属性。
每个类都实现了其 CanvasEntityProtocol
的特有版本。
EntityTilded
CanvasEntityTilted
扩展了 CanvasEntityLocated
,并在 GeometricsTilt Tilt
成员中添加了一个旋转角度。
Square、Rectangle、Ellipse 和 Triangle
CanvasSquare
、CanvasRectangle
、CanvasEllipse
和 CanvasTriangle
是基于 CanvasEntityTilted
的具体类。每个类都添加了一些属性来完成其形状定义(请参阅代码),并实现了其 CanvasEntityProtocol
的特有版本。
Canvas 类
Canvas
类充当所有其他类的容器和粘合剂。即便如此,它也不是一个最终的具体类,因为根据要求,我们需要一个完全隐藏且可透明替换的数据结构来收集形状。
因此,Canvas
实际上是作为其子类 CanvasImplementation
实例化的,它嵌入了一个简单的链表来收集形状。它还提供了一个 CanvasEntityIterator
的实现,即 CanvasEntityListIterator
,用于遍历列表,并实现了完整的 CanvasEntityCollectionProtocol
。
CanvasRenderer 类
CanvasRenderer
是一个 抽象
接口
,它公开了渲染每个已知形状的方法,如下所示:
renderPoint
renderCircle
renderSquare
renderRectangle
renderEllipse
renderTriangle
必须为任何设备实现此接口,或以我们希望渲染 canvas
内容的方式实现。
在本示例中,渲染通过 ConsoleRenderer
类实现,该类仅将提交给它的形状信息打印到控制台。
关注点
除了可以或多或少设计得很好的层次结构之外,我想在此重点介绍如何在纯 C 语言中管理这些类。在此问题中,我想重点关注两个方面:
- 宏辅助
- 动态分配
宏辅助
为了解决在每个类扩展/接口实现中需要重复大量声明符所带来的固有开销,我引入了预处理器宏的使用,作为类定义过程的组成部分。
类组件定义辅助
用于收集特定类的所有数据组件,并用于其所有子类。
例如,CanvasEntity_c
、CanvasEntityLocated_c
等。
struct CanvasEntity {
{…}
#define CanvasEntity_c
struct CanvasEntityAttributes Attributes[1];
};
{…}
struct CanvasEntityLocated {
{…}
#define CanvasEntityLocated_c CanvasEntity_c\
struct GeometricsLocation2D Location[1];
CanvasEntityLocated_c
};
struct CanvasPoint {
{…}
CanvasEntityLocated_c
};
在上图中,我们可以看到 CanvasEntity_c
的定义如何嵌入 CanvasEntity
的主体中,它如何被 继承 到 CanvasEntityLocated_c
中,最后,CanvasPoint
如何使用后者来包含所有需要继承的成员。
方法参数列表定义辅助
用于收集函数的部分或全部参数列表声明,并用于加快这些方法的实现速度。
例如,我们可以看到 CanvassEntity_scale_al
的用法。
{…}
#define CanvasEntity_scale_al GeometricsMeasure originX,
GeometricsMeasure originY, GeometricsMeasure scaleX, GeometricsMeasure scaleY, CoreInt32 scaleUnit
CoreResultCode(*scale)(CoreTarget target, CanvasEntity_scale_al);
{…}
CoreResultCode CanvasPoint_Entity_scale(CoreTarget target, CanvasEntity_scale_al);
{…}
CoreResultCode CanvasCircle_Entity_scale(CoreTarget target, CanvasEntity_scale_al);
{…}
CoreResultCode CanvasSquare_Entity_scale(CoreTarget target, CanvasEntity_scale_al);
CanvassEntity_scale_al
定义在 CanvasEntiryProtocol
中 scale 方法的相同位置,以及所有 scale 实现的声明中。用法相同。
另一个例子是宏对 CanvasEntityTilted_build_bal
和 CanvasEntityTilted_build_eal
的使用,它们一起嵌入了一个公共声明,但允许在两者之间插入其他参数。
#define CanvasEntityTilted_build_bal CanvasEntityLocated_build_bal
#define CanvasEntityTilted_build_eal GeometricsMeasure rotationAngle, CanvasEntityLocated_build_eal
struct CanvasEntityTilted* CanvasEntityTilted_init(CoreTarget target, CanvasEntityTilted_build_bal,
CanvasEntityTilted_build_eal);
{…}
struct CanvasRectangle* CanvasRectangle_build(CoreMemoryAddress target, CanvasEntityTilted_build_bal
, GeometricsMeasure width, GeometricsMeasure height, CanvasEntityTilted_build_eal);
struct CanvasRectangle* CanvasRectangle_init(CoreTarget target, CanvasEntityTilted_build_bal
, GeometricsMeasure width, GeometricsMeasure height, CanvasEntityTilted_build_eal);
动态分配
在此示例中,我为构建器添加了动态分配功能,并提供了一种通用的解除分配方法。
分配
分配通过 CoreObject_build
实用函数完成。
struct CoreObject* CoreObject_build(CoreTarget target, CoreTarget descriptor) {
struct CoreObject* self = (struct CoreObject*)target;
const struct CoreObjectDescriptor* type = (const struct CoreObjectDescriptor*)descriptor;
if(self == 0) {
self = CoreMemory_allocBytes(type->Size);
}
CoreMemory_clearBytes(self, type->Size);
self->invoke = type->Interface;
return self;
}
解除分配
通过调用 CoreObject_free
函数并向其提供要释放的实例地址来执行解除分配。该函数所做的只是向实例类的 tell 方法发送一个 CoreObject_tell_Free
消息。实现类有责任最终处理该消息的实际解除分配。
CoreObject_free
函数
CoreResultCode CoreObject_free(CoreTarget target) {
struct CoreObject* self = (struct CoreObject*)target;
if(self == 0) return CoreResultCode_Success;
return self->invoke->Class->tell(target, CoreObject_tell_Free);
}
处理 CoreObject_tell_Free
消息的 tell
函数示例
CoreResultCode Canvas_tell(CoreTarget target, CoreWord16 message, ...) {
switch(message) {
case CoreObject_tell_Free:
if(target) {
Canvas_Collection_freeAll(target);
CoreMemory_free((CoreMemoryAddress)target);
}
}
return CoreResultCode_Success;
}
如何使用源代码
随附的源代码是将代码片段整合到一个 C 文件中,以便立即将重点放在主题上。该软件包还包括创建它的 VisualStudio2017 Community Edition 项目和解决方案。请随意修改并享用。
待续...
这个主题的广阔性无法在一两篇文章中容纳,这只是一个简要的概述。如果能引起一些兴趣,我将回到这个主题,来处理使用 CoreObjects/ColiahCore17
的 OOP
的其他一些方面,从通用 PC 到资源非常有限的嵌入式架构。
历史
- 2018年12月26日:发布《CoreObjects/GoliahCore17:纯 C 语言中的高效 OOP》
- 2018年12月29日:发布《CoreObjects/GoliahCore17:纯 C 语言中的高效 OOP || 2》