一组漂亮的预处理器黑客(ANSI C89 / ISO C90兼容)会在C中实现某种丑陋(但可用)的面向对象吗?
我熟悉一些不同的面向对象语言,所以请不要回答“学习C ++!”等答案。我读过“Object-Oriented Programming With ANSI C”(请注意: PDF格式)以及其他一些有趣的解决方案,但我最感兴趣的是你: - )!
答案 0 :(得分:175)
我建议不要使用预处理器(ab)来尝试使C语法更像是另一种更面向对象的语言。在最基本的层面上,您只需使用普通结构作为对象并通过指针传递它们:
struct monkey
{
float age;
bool is_male;
int happiness;
};
void monkey_dance(struct monkey *monkey)
{
/* do a little dance */
}
要获得继承和多态这样的东西,你必须更努力地工作。您可以通过让结构的第一个成员成为超类的实例来进行手动继承,然后您可以自由地转换指向基类和派生类的指针:
struct base
{
/* base class members */
};
struct derived
{
struct base super;
/* derived class members */
};
struct derived d;
struct base *base_ptr = (struct base *)&d; // upcast
struct derived *derived_ptr = (struct derived *)base_ptr; // downcast
要获得多态(即虚函数),可以使用函数指针,也可以使用函数指针表,也称为虚拟表或vtable:
struct base;
struct base_vtable
{
void (*dance)(struct base *);
void (*jump)(struct base *, int how_high);
};
struct base
{
struct base_vtable *vtable;
/* base members */
};
void base_dance(struct base *b)
{
b->vtable->dance(b);
}
void base_jump(struct base *b, int how_high)
{
b->vtable->jump(b, how_high);
}
struct derived1
{
struct base super;
/* derived1 members */
};
void derived1_dance(struct derived1 *d)
{
/* implementation of derived1's dance function */
}
void derived1_jump(struct derived1 *d, int how_high)
{
/* implementation of derived 1's jump function */
}
/* global vtable for derived1 */
struct base_vtable derived1_vtable =
{
&derived1_dance, /* you might get a warning here about incompatible pointer types */
&derived1_jump /* you can ignore it, or perform a cast to get rid of it */
};
void derived1_init(struct derived1 *d)
{
d->super.vtable = &derived1_vtable;
/* init base members d->super.foo */
/* init derived1 members d->foo */
}
struct derived2
{
struct base super;
/* derived2 members */
};
void derived2_dance(struct derived2 *d)
{
/* implementation of derived2's dance function */
}
void derived2_jump(struct derived2 *d, int how_high)
{
/* implementation of derived2's jump function */
}
struct base_vtable derived2_vtable =
{
&derived2_dance,
&derived2_jump
};
void derived2_init(struct derived2 *d)
{
d->super.vtable = &derived2_vtable;
/* init base members d->super.foo */
/* init derived1 members d->foo */
}
int main(void)
{
/* OK! We're done with our declarations, now we can finally do some
polymorphism in C */
struct derived1 d1;
derived1_init(&d1);
struct derived2 d2;
derived2_init(&d2);
struct base *b1_ptr = (struct base *)&d1;
struct base *b2_ptr = (struct base *)&d2;
base_dance(b1_ptr); /* calls derived1_dance */
base_dance(b2_ptr); /* calls derived2_dance */
base_jump(b1_ptr, 42); /* calls derived1_jump */
base_jump(b2_ptr, 42); /* calls derived2_jump */
return 0;
}
这就是你在C中做多态的方法。它并不漂亮,但是它确实起到了作用。有一些棘手的问题涉及基类和派生类之间的指针转换,只要基类是派生类的第一个成员,它们是安全的。多重继承要困难得多 - 在这种情况下,为了除了第一个之外的基类之间的情况,你需要根据正确的偏移量手动调整指针,这非常棘手且容易出错。
您可以做的另一件(棘手的事)是在运行时更改对象的动态类型!你只需重新分配一个新的vtable指针。您甚至可以选择性地更改某些虚拟功能,同时保留其他功能,从而创建新的混合类型。只是要小心创建一个新的vtable而不是修改全局vtable,否则你会意外地影响给定类型的所有对象。
答案 1 :(得分:31)
我曾经和一个C库一起工作过,这个C库以一种让我感到非常优雅的方式实现。他们用C语言编写了一种定义对象的方法,然后从它们继承,以便它们像C ++对象一样可扩展。基本的想法是:
继承很难描述,但基本上就是这样:
struct vehicle {
int power;
int weight;
}
然后在另一个文件中:
struct van {
struct vehicle base;
int cubic_size;
}
然后你可以在内存中创建一辆面包车,并由只知道车辆的代码使用:
struct van my_van;
struct vehicle *something = &my_van;
vehicle_function( something );
它运行得很漂亮,.h文件确切地定义了你应该对每个对象做什么。
答案 2 :(得分:29)
C Object System (COS)听起来很有希望(它仍然是alpha版本)。为了简单和灵活,它试图保持最小的可用概念:统一的面向对象编程,包括开放类,元类,属性元类,泛型,多方法,委托,所有权,异常,契约和闭包。有draft paper(PDF)描述它。
Exception in C是在其他OO语言中找到的TRY-CATCH-FINALLY的C89实现。它附带了一个测试套件和一些例子。
劳伦特·丹尼奥(Laurent Deniau),他在OOP in C上工作很多。
答案 3 :(得分:16)
用于Linux的GNOME桌面是用面向对象的C编写的,它有一个名为“GObject”的对象模型,它支持属性,继承,多态,以及一些其他好东西,如引用,事件处理(称为“信号”),运行时输入,私人数据等。
它包括预处理器黑客来处理类层次结构中的类型转换等等。这是我为GNOME编写的一个示例类(像gchar这样的东西是typedef):
在GObject结构中,有一个GType整数,用作GLib动态类型系统的幻数(你可以将整个结构转换为“GType”来找到它的类型)。
答案 4 :(得分:6)
如果您将对象调用的方法视为将隐式“this
”传递给函数的静态方法,则可以使C中的OO更容易思考。
例如:
String s = "hi";
System.out.println(s.length());
变为:
string s = "hi";
printf(length(s)); // pass in s, as an implicit this
或类似的东西。
答案 5 :(得分:6)
答案 6 :(得分:5)
在我知道OOP是什么之前,我曾经在C中做过这种事情。
以下是一个示例,它实现了一个数据缓冲区,该缓冲区根据需要增长,给定最小大小,增量和最大大小。这个特定的实现是基于“元素”的,也就是说它被设计为允许任何C类型的类似列表的集合,而不仅仅是一个可变长度的字节缓冲区。
这个想法是使用xxx_crt()实例化对象并使用xxx_dlt()删除。每个“成员”方法都采用特定类型的指针来操作。
我以这种方式实现了一个链表,循环缓冲区和许多其他东西。
我必须承认,我从未考虑如何使用这种方法实现继承。我想Kieveli提供的一些混合可能是一条很好的道路。
dtb.c:
#include <limits.h>
#include <string.h>
#include <stdlib.h>
static void dtb_xlt(void *dst, const void *src, vint len, const byte *tbl);
DTABUF *dtb_crt(vint minsiz,vint incsiz,vint maxsiz) {
DTABUF *dbp;
if(!minsiz) { return NULL; }
if(!incsiz) { incsiz=minsiz; }
if(!maxsiz || maxsiz<minsiz) { maxsiz=minsiz; }
if(minsiz+incsiz>maxsiz) { incsiz=maxsiz-minsiz; }
if((dbp=(DTABUF*)malloc(sizeof(*dbp))) == NULL) { return NULL; }
memset(dbp,0,sizeof(*dbp));
dbp->min=minsiz;
dbp->inc=incsiz;
dbp->max=maxsiz;
dbp->siz=minsiz;
dbp->cur=0;
if((dbp->dta=(byte*)malloc((vuns)minsiz)) == NULL) { free(dbp); return NULL; }
return dbp;
}
DTABUF *dtb_dlt(DTABUF *dbp) {
if(dbp) {
free(dbp->dta);
free(dbp);
}
return NULL;
}
vint dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen) {
if(!dbp) { errno=EINVAL; return -1; }
if(dtalen==-1) { dtalen=(vint)strlen((byte*)dtaptr); }
if((dbp->cur + dtalen) > dbp->siz) {
void *newdta;
vint newsiz;
if((dbp->siz+dbp->inc)>=(dbp->cur+dtalen)) { newsiz=dbp->siz+dbp->inc; }
else { newsiz=dbp->cur+dtalen; }
if(newsiz>dbp->max) { errno=ETRUNC; return -1; }
if((newdta=realloc(dbp->dta,(vuns)newsiz))==NULL) { return -1; }
dbp->dta=newdta; dbp->siz=newsiz;
}
if(dtalen) {
if(xlt256) { dtb_xlt(((byte*)dbp->dta+dbp->cur),dtaptr,dtalen,xlt256); }
else { memcpy(((byte*)dbp->dta+dbp->cur),dtaptr,(vuns)dtalen); }
dbp->cur+=dtalen;
}
return 0;
}
static void dtb_xlt(void *dst,const void *src,vint len,const byte *tbl) {
byte *sp,*dp;
for(sp=(byte*)src,dp=(byte*)dst; len; len--,sp++,dp++) { *dp=tbl[*sp]; }
}
vint dtb_addtxt(DTABUF *dbp,const byte *xlt256,const byte *format,...) {
byte textÝ501¨;
va_list ap;
vint len;
va_start(ap,format); len=sprintf_len(format,ap)-1; va_end(ap);
if(len<0 || len>=sizeof(text)) { sprintf_safe(text,sizeof(text),"STRTOOLNG: %s",format); len=(int)strlen(text); }
else { va_start(ap,format); vsprintf(text,format,ap); va_end(ap); }
return dtb_adddta(dbp,xlt256,text,len);
}
vint dtb_rmvdta(DTABUF *dbp,vint len) {
if(!dbp) { errno=EINVAL; return -1; }
if(len > dbp->cur) { len=dbp->cur; }
dbp->cur-=len;
return 0;
}
vint dtb_reset(DTABUF *dbp) {
if(!dbp) { errno=EINVAL; return -1; }
dbp->cur=0;
if(dbp->siz > dbp->min) {
byte *newdta;
if((newdta=(byte*)realloc(dbp->dta,(vuns)dbp->min))==NULL) {
free(dbp->dta); dbp->dta=null; dbp->siz=0;
return -1;
}
dbp->dta=newdta; dbp->siz=dbp->min;
}
return 0;
}
void *dtb_elmptr(DTABUF *dbp,vint elmidx,vint elmlen) {
if(!elmlen || (elmidx*elmlen)>=dbp->cur) { return NULL; }
return ((byte*)dbp->dta+(elmidx*elmlen));
}
dtb.h
typedef _Packed struct {
vint min; /* initial size */
vint inc; /* increment size */
vint max; /* maximum size */
vint siz; /* current size */
vint cur; /* current data length */
void *dta; /* data pointer */
} DTABUF;
#define dtb_dtaptr(mDBP) (mDBP->dta)
#define dtb_dtalen(mDBP) (mDBP->cur)
DTABUF *dtb_crt(vint minsiz,vint incsiz,vint maxsiz);
DTABUF *dtb_dlt(DTABUF *dbp);
vint dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen);
vint dtb_addtxt(DTABUF *dbp,const byte *xlt256,const byte *format,...);
vint dtb_rmvdta(DTABUF *dbp,vint len);
vint dtb_reset(DTABUF *dbp);
void *dtb_elmptr(DTABUF *dbp,vint elmidx,vint elmlen);
PS:vint只是int的typedef - 我用它来提醒我,它的长度在平台之间是可变的(用于移植)。
答案 7 :(得分:4)
ffmpeg(用于视频处理的工具包)是用直接C(和汇编语言)编写的,但使用面向对象的样式。它充满了带有函数指针的结构体。有一组工厂函数使用适当的“方法”指针初始化结构。
答案 8 :(得分:3)
如果您真的认为是餐饮,即使标准C库也使用OOP - 请以FILE *
为例:fopen()
初始化FILE *
对象,并使用它使用成员方法{{1 }},fscanf()
,fprintf()
,fread()
和其他人,最终用fwrite()
完成。
您也可以采用伪Objective-C方式,这也不难:
fclose()
使用:
typedef void *Class;
typedef struct __class_Foo
{
Class isa;
int ivar;
} Foo;
typedef struct __meta_Foo
{
Foo *(*alloc)(void);
Foo *(*init)(Foo *self);
int (*ivar)(Foo *self);
void (*setIvar)(Foo *self);
} meta_Foo;
meta_Foo *class_Foo;
void __meta_Foo_init(void) __attribute__((constructor));
void __meta_Foo_init(void)
{
class_Foo = malloc(sizeof(meta_Foo));
if (class_Foo)
{
class_Foo = {__imp_Foo_alloc, __imp_Foo_init, __imp_Foo_ivar, __imp_Foo_setIvar};
}
}
Foo *__imp_Foo_alloc(void)
{
Foo *foo = malloc(sizeof(Foo));
if (foo)
{
memset(foo, 0, sizeof(Foo));
foo->isa = class_Foo;
}
return foo;
}
Foo *__imp_Foo_init(Foo *self)
{
if (self)
{
self->ivar = 42;
}
return self;
}
// ...
如果使用了相当古老的Objective-C-to-C转换器,这可能是由于某些Objective-C代码造成的:
int main(void)
{
Foo *foo = (class_Foo->init)((class_Foo->alloc)());
printf("%d\n", (foo->isa->ivar)(foo)); // 42
foo->isa->setIvar(foo, 60);
printf("%d\n", (foo->isa->ivar)(foo)); // 60
free(foo);
}
答案 9 :(得分:3)
我认为Adam Rosenfield发布的是在C中进行OOP的正确方法。我想补充一点,他所展示的是对象的实现。换句话说,实际的实现将放在.c
文件中,而接口将放在标头.h
文件中。例如,使用上面的猴子示例:
界面如下:
//monkey.h
struct _monkey;
typedef struct _monkey monkey;
//memory management
monkey * monkey_new();
int monkey_delete(monkey *thisobj);
//methods
void monkey_dance(monkey *thisobj);
您可以在界面.h
文件中看到您只定义原型。然后,您可以将实现部分“.c
file”编译到静态或动态库中。这会创建封装,您也可以随意更改实现。对象的用户需要几乎不了解它的实现。这也将重点放在对象的整体设计上。
我个人认为oop是一种概念化代码结构和可重用性的方式,并且与添加到c ++(如重载或模板)的其他内容实际上没有任何关系。是的,这些是非常好用的功能,但它们并不代表面向对象编程的真正含义。
答案 10 :(得分:1)
如果我要在C中编写OOP,我可能会采用伪 - Pimpl设计。您最终会将指针传递给指向结构的指针,而不是将指针传递给结构体。这使得内容不透明并促进多态性和继承。
C中OOP的真正问题是当变量退出范围时会发生什么。没有编译器生成的析构函数,这可能会导致问题。 Macros可能有所帮助,但看起来总是很难看。
答案 11 :(得分:1)
我的建议:保持简单。我遇到的最大问题之一是维护旧软件(有时超过10年)。如果代码不简单,则可能很难。是的,人们可以在C中编写非常有用的OOP和多态,但它很难阅读。
我更喜欢封装一些明确定义的功能的简单对象。一个很好的例子是GLIB2,例如哈希表:
GHastTable* my_hash = g_hash_table_new(g_str_hash, g_str_equal);
int size = g_hash_table_size(my_hash);
...
g_hash_table_remove(my_hash, some_key);
关键是:
答案 12 :(得分:1)
使用C进行面向对象样式编程的另一种方法是使用代码生成器,该代码生成器将特定于域的语言转换为C。就像使用TypeScript和JavaScript将OOP引入js一样。
答案 13 :(得分:1)
您可以尝试COOP,它是C语言中面向程序员的友好编程框架,具有类,异常,多态性和内存管理(对于嵌入式代码很重要)。这是一种相对轻量级的语法,请参见此处的tutorial in the Wiki 。
答案 14 :(得分:0)
#include "triangle.h"
#include "rectangle.h"
#include "polygon.h"
#include <stdio.h>
int main()
{
Triangle tr1= CTriangle->new();
Rectangle rc1= CRectangle->new();
tr1->width= rc1->width= 3.2;
tr1->height= rc1->height= 4.1;
CPolygon->printArea((Polygon)tr1);
printf("\n");
CPolygon->printArea((Polygon)rc1);
}
输出:
6.56
13.12
以下是使用C语言进行OO编程的演示。
这是真正的纯C,没有预处理器宏。我们有继承, 多态性和数据封装(包括对类或对象私有的数据)。 保护资格者没有机会等同,即 私人数据也属于无遗嘱链。 但这不是一个不便,因为我认为没有必要。
CPolygon
未实例化,因为我们仅使用它来操纵对象
在具有共同方面但不同的传承链中
它们的实现(多态)。
答案 15 :(得分:0)
@Adam Rosenfield对如何用C
实现OOP有一个非常好的解释此外,我建议你阅读
1)pjsip
一个非常好的VoIP库。您可以通过结构和函数指针表了解如何实现OOP
了解iOS Runtime如何为Objective C提供支持。它通过isa指针,元类
实现OOP答案 16 :(得分:0)
对我来说,C中的面向对象应具有以下功能:
封装和数据隐藏(可以使用结构/不透明指针实现)
对多态的继承和支持(使用结构可以实现单继承 - 确保抽象基不可实现)
构造函数和析构函数功能(不易实现)
类型检查(至少对于用户定义的类型,因为C不强制执行)
引用计数(或要实现的内容RAII)
对异常处理的有限支持(setjmp和longjmp)
除此之外,它应该依赖于ANSI / ISO规范,不应该依赖于编译器特定的功能。
答案 17 :(得分:0)
看看http://ldeniau.web.cern.ch/ldeniau/html/oopc/oopc.html。如果没有别的东西通过文档阅读是一个启发性的经验。
答案 18 :(得分:0)
我在这里参加派对有点晚了但是我喜欢避免两个宏极端 - 太多或太多混淆代码,但是一些明显的宏可以使OOP代码更容易开发并阅读:
/*
* OOP in C
*
* gcc -o oop oop.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
struct obj2d {
float x; // object center x
float y; // object center y
float (* area)(void *);
};
#define X(obj) (obj)->b1.x
#define Y(obj) (obj)->b1.y
#define AREA(obj) (obj)->b1.area(obj)
void *
_new_obj2d(int size, void * areafn)
{
struct obj2d * x = calloc(1, size);
x->area = areafn;
// obj2d constructor code ...
return x;
}
// --------------------------------------------------------
struct rectangle {
struct obj2d b1; // base class
float width;
float height;
float rotation;
};
#define WIDTH(obj) (obj)->width
#define HEIGHT(obj) (obj)->height
float rectangle_area(struct rectangle * self)
{
return self->width * self->height;
}
#define NEW_rectangle() _new_obj2d(sizeof(struct rectangle), rectangle_area)
// --------------------------------------------------------
struct triangle {
struct obj2d b1;
// deliberately unfinished to test error messages
};
#define NEW_triangle() _new_obj2d(sizeof(struct triangle), triangle_area)
// --------------------------------------------------------
struct circle {
struct obj2d b1;
float radius;
};
#define RADIUS(obj) (obj)->radius
float circle_area(struct circle * self)
{
return M_PI * self->radius * self->radius;
}
#define NEW_circle() _new_obj2d(sizeof(struct circle), circle_area)
// --------------------------------------------------------
#define NEW(objname) (struct objname *) NEW_##objname()
int
main(int ac, char * av[])
{
struct rectangle * obj1 = NEW(rectangle);
struct circle * obj2 = NEW(circle);
X(obj1) = 1;
Y(obj1) = 1;
// your decision as to which of these is clearer, but note above that
// macros also hide the fact that a member is in the base class
WIDTH(obj1) = 2;
obj1->height = 3;
printf("obj1 position (%f,%f) area %f\n", X(obj1), Y(obj1), AREA(obj1));
X(obj2) = 10;
Y(obj2) = 10;
RADIUS(obj2) = 1.5;
printf("obj2 position (%f,%f) area %f\n", X(obj2), Y(obj2), AREA(obj2));
// WIDTH(obj2) = 2; // error: struct circle has no member named width
// struct triangle * obj3 = NEW(triangle); // error: triangle_area undefined
}
我认为这有很好的平衡,并且它产生的错误(至少对于默认的gcc 6.3选项)对于一些更可能的错误是有帮助的,而不是混淆。重点是提高程序员的工作效率吗?
答案 19 :(得分:0)
我也在基于宏解决方案进行此工作。我想这仅是最勇敢的;-)但它已经相当不错了,我已经在上面进行了一些项目。 它可以正常工作,以便您首先为每个类定义一个单独的头文件。像这样:
#define CLASS Point
#define BUILD_JSON
#define Point__define \
METHOD(Point,public,int,move_up,(int steps)) \
METHOD(Point,public,void,draw) \
\
VAR(read,int,x,JSON(json_int)) \
VAR(read,int,y,JSON(json_int)) \
要实现该类,请为其创建一个头文件,并在其中实现方法的C文件中创建一个
METHOD(Point,public,void,draw)
{
printf("point at %d,%d\n", self->x, self->y);
}
在为类创建的标题中,包括所需的其他标题并定义与该类相关的类型等。在类头文件和C文件中,都包括类规范文件(请参见第一个代码示例)和一个X宏。这些X宏(1,2,3等)会将代码扩展到实际的类结构和其他声明。
要继承类,请#define SUPER supername
并将supername__define \
添加为类定义的第一行。两者都必须在那里。还提供JSON支持,信号,抽象类等。
要创建对象,只需使用W_NEW(classname, .x=1, .y=2,...)
。初始化基于C11中引入的struct初始化。效果很好,未列出的所有内容均设为零。
要调用方法,请使用W_CALL(o,method)(1,2,3)
。它看起来像一个高阶函数调用,但它只是一个宏。它扩展为((o)->klass->method(o,1,2,3))
,这是一个非常不错的技巧。
由于框架需要一些样板代码,因此我编写了一个Perl脚本(wobject)来完成这项工作。如果使用它,就可以写
class Point
public int move_up(int steps)
public void draw()
read int x
read int y
,它将创建类规范文件,类头文件和一个C文件,其中包括实现类的Point_impl.c
。如果您有很多简单的类,但是仍然所有内容都在C中,那么它可以节省很多工作。wobject是一个非常简单的基于正则表达式的扫描程序,可以轻松满足特定需求或从头开始重写。
答案 20 :(得分:0)
开源Dynace项目正是这样做的。在https://github.com/blakemcbride/Dynace
答案 21 :(得分:-2)
如果你需要编写一些代码 试试这个:https://github.com/fulminati/class-framework
#include "class-framework.h"
CLASS (People) {
int age;
};
int main()
{
People *p = NEW (People);
p->age = 10;
printf("%d\n", p->age);
}
答案 22 :(得分:-2)
我有一个关于这个主题的小型项目。它支持封装、单继承和多态。如果有人仍然对此感兴趣,请访问该存储库的链接:https://github.com/alexmarincu/Cbject
这是一个语法示例:
形状.h:
#ifndef SHAPE_H
#define SHAPE_H
#include "../Cbj/Cbj.h" // includes Cbject (base object) and macros
#include "Point.h"
#define Type Shape
#define Parent Cbject
AbstractClass(
Params(_, Point origin),
Props(_, Point origin),
VirtFuns(_,
(float, area, (0)),
(void, draw, (_, uint8 const a))));
Setters(_, (Point, origin));
Getters(_, (Point, origin));
SuperFun(void, draw, (_, uint8 const a));
#undef Parent
#undef Type
#endif // SHAPE_H
形状.c:
#include "Shape.h"
#define Type Shape
#define Parent Cbject
AbstractClassSetup(
VirtFunCalls(_,
(float, area, (0), (0)),
(void, draw, (_, uint8 const a), (_, a))),
BindFuns(_,
(void, Shape, draw, (_, uint8 const a))));
Init { me->p.origin = params->origin; }
Terminate {}
DefaultSet(Point, origin);
DefaultGet(Point, origin);
SuperFun(void, draw, (_, uint8 const a))
{
// Default implementation of draw function
}
#undef Parent
#undef Type
Circle.h:
#ifndef CIRCLE_H
#define CIRCLE_H
#include "Shape.h"
#define Type Circle
#define Parent Shape
Class(
Params(_,
Point origin,
uint32 radius),
Props(_, int32 radius),
VirtFuns(0));
Set(uint32, radius);
Get(uint32, radius);
SuperFuns(_,
(float, area, (0)),
(void, draw, (_, uint8 const a)));
#undef Type
#undef Parent
#endif // CIRCLE_H
Circle.c:
#include "Circle.h"
#define Type Circle
#define Parent Shape
ClassSetup(
VirtFunCalls(_, (void, rotate, (0), (0))),
BindFuns(_,
(float, Shape, area, (0)),
(void, Shape, draw, (_, uint8 const a))));
PrivateConst(float, pi = 3.14);
Init
{
*s_params = (ShapeParams){
.origin.x = params->origin.x,
.origin.y = params->origin.y};
me->p.radius = params->radius;
}
Terminate {}
DefaultSet(uint32, radius);
DefaultGet(uint32, radius);
SuperFun(void, draw, (_, uint8 const a))
{
s_Shape_draw((Shape *) me, a); // Calling overridden function from parent
// Specific implementation of draw function
}
SuperFun(float, area, (0)) { return me->p.radius * me->p.radius * Circle_pi; } // Specific implementation of area function
#undef Parent
#undef Type
用法:
...
{
Circle * circle = New_Circle(&((CircleParams){.origin.x = 0, .origin.y = 1, .radius = 1})); // create circle
uint32 radius = Circle_radius(circle)); // get radius
Circle_radiusSet(circle, 2); // set radius
float area = Shape_area(Circle_toShape(circle))); // polymorphic call
Delete_Circle(circle);
}
尚无可用文档,但示例应该相当容易理解。