人们使用什么技术/策略来构建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中的对象这样的事情时,我倾向于采用最简单的方法。

问以下问题:

  1. 这是针对有截止日期的客户(如果是,请考虑OOP)?
  2. 我可以使用OOP(通常更少的代码,更快的开发,更可读)?
  3. 我可以使用库(现有代码,现有模板)吗?
  4. 我受内存或CPU的限制(例如Arduino)?
  5. 使用C还有其他技术原因吗?
  6. 我可以保持我的C非常简单和可读吗?
  7. 我的项目真正需要什么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如何在内部表示解析树节点,表达式类型等。 有一些默认的NodeExpr结构

 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内部。 开发人员常见问题解答有更多信息。)