人们使用什么技术/策略来构建C(而不是C ++)中的对象?
我特别感兴趣的是在C语言中使用的对象,而不是构成解释语言核心的对象的实现,例如python。
我倾向于这样做:
struct foo_ops { void (*blah)(struct foo *, ...); void (*plugh)(struct foo *, ...); }; struct foo { struct foo_ops *ops; /* data fields for foo go here */ };
使用这些结构定义,实现foo的代码看起来像这样:
static void plugh(struct foo *, ...) { ... } static void blah(struct foo *, ...) { ... } static struct foo_ops foo_ops = { blah, plugh }; struct foo *new_foo(...) { struct foo *foop = malloc(sizeof(*foop)); foop->ops = &foo_ops; /* fill in rest of *foop */ return foop; }
然后,在使用foo的代码中:
struct foo *foop = new_foo(...); foop->ops->blah(foop, ...); foop->ops->plugh(foop, ...);
此代码可以使用宏或内联函数进行整理,因此它看起来更像C
foo_blah(foop, ...); foo_plugh(foop, ...);
虽然如果你坚持使用一个相当短的名称“ops”字段,只需写出原来显示的代码并不是特别冗长。
这种技术完全适用于在C中实现相对简单的基于对象的设计,但它不能处理更高级的要求,例如显式表示类和方法inheritance。 对于那些,你可能需要像GObject(如EFraim所提到的),但我建议确保你真的需要更复杂的框架的额外function。
你对术语“对象”的使用有点模糊,所以我假设你在问如何使用C来实现面向对象编程的某些方面(在这个假设下随意纠正我。)
方法多态性:
方法多态性通常使用函数指针在C中模拟。 例如,如果我有一个我用来表示image_scaler的结构(需要一个图像并将其调整为新尺寸),我可以这样做:
struct image_scaler { //member variables int (*scale)(int, int, int*); }
然后,我可以制作几个图像缩放器:
struct image_scaler nn, bilinear; nn->scale = &nearest_neighbor_scale; bilinear->scale = &bilinear_scale;
这让我可以为任何接受image_scaler的函数实现多态行为,并通过简单地传递一个不同的image_scaler来使用它的缩放方法。
遗产
通常通过以下方式实现inheritance:
struct base{ int x; int y; } struct derived{ struct base; int z; }
现在,我可以自由地使用派生的额外字段,以及获取所有“inheritance”的字段。 此外,如果您有一个只接受结构基础的函数。 您可以简单地将struct dervied指针转换为struct base指针,而不会产生任何后果
像GObject这样的库。
基本上GObject提供了描述不透明值(整数,字符串)和对象的常用方法(通过手动描述接口 – 作为函数指针的结构,基本上是在C ++中与VTable相对应) – 有关结构的更多信息可以在其参考中找到
您通常也会手动实现vtable,如“在普通C中的COM”
从浏览所有答案可以看出,有库,函数指针,inheritance方法,封装等等,都可用(C ++最初是C的前端)。
但是,我发现软件的一个非常重要的方面是可读性。 您是否尝试过阅读10年前的代码? 因此,在做像C中的对象这样的事情时,我倾向于采用最简单的方法。
问以下问题:
- 这是针对有截止日期的客户(如果是,请考虑OOP)?
- 我可以使用OOP(通常更少的代码,更快的开发,更可读)?
- 我可以使用库(现有代码,现有模板)吗?
- 我受内存或CPU的限制(例如Arduino)?
- 使用C还有其他技术原因吗?
- 我可以保持我的C非常简单和可读吗?
- 我的项目真正需要什么OOPfunction?
我通常会回复到像GLIB API这样的东西,它允许我封装我的代码并提供一个非常易读的界面。 如果需要更多,我添加多态的函数指针。
class_A.h: typedef struct _class_A {...} Class_A; Class_A* Class_A_new(); void Class_A_empty(); ... #include "class_A.h" Class_A* my_instance; my_instance = Class_A_new(); my_instance->Class_A_empty(); // can override using function pointers
看看IJG的实现。 他们不仅使用setjmp / longjmp进行exception处理,还拥有vtable和一切。 这是一个写得很好,足够小的库,可以让你得到一个很好的例子。
与Dale的方法类似,但是更多的一个步枪是PostgreSQL如何在内部表示解析树节点,表达式类型等。 有一些默认的Node
和Expr
结构
typedef struct { NodeTag n; } Node;
其中NodeTag
是unsigned int的typedef,并且有一个头文件,其中包含一堆描述所有可能节点类型的常量。 节点本身看起来像这样:
typedef struct { NodeTag n = FOO_NODE; /* other members go here */ } FooNode;
由于C结构的怪癖,因此可以将FooNode
转换为Node
而不受惩罚:如果两个结构具有相同的第一个成员,则它们可以相互投射。
是的,这意味着可以将FooNode
转换为BarNode
,您可能不想这样做。 如果你想要正确的运行时类型检查,GObject是可行的方法,但是当你掌握它时,准备讨厌生活。
(注意:来自内存的例子,我在一段时间内没有攻击Postgres内部。 开发人员常见问题解答有更多信息。)