7

我对打算在 C 中使用的对象特别感兴趣,而不是构成解释语言(如 python)核心的对象实现。

4

6 回答 6

9

我倾向于做这样的事情:

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 中实现相对简单的基于对象的设计,但它不能处理更高级的要求,例如显式表示类和方法继承。对于这些,您可能需要 GObject 之类的东西(正如 EFraim 所提到的),但我建议确保您确实需要更复杂框架的额外功能。

于 2009-08-04T07:05:18.590 回答
7

您对“对象”一词的使用有点含糊,所以我假设您在问如何使用 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 来使用它的 scale 方法。

遗产

继承通常是这样实现的:

struct base{
   int x;
   int y;
} 

struct derived{
   struct base;
   int z;
}

现在,我可以自由使用派生的额外字段,以及获取 base 的所有“继承”字段。此外,如果您有一个只接受结构基础的函数。您可以简单地将您的结构派生指针转换为结构基指针,而不会产生任何后果

于 2009-08-04T07:39:50.080 回答
4

GObject等库。

基本上,GObject 提供了描述不透明值(整数、字符串)和对象的常用方法(通过手动描述接口 - 作为函数指针的结构,基本上对应于 C++ 中的 VTable) - 有关结构的更多信息可以在其参考中找到

您通常还会像“COM in plain C”中那样手动实现 vtables

于 2009-08-04T05:50:10.077 回答
3

浏览所有答案可以看到,有库、函数指针、继承方式、封装等,一应俱全(C++原本是C的前端)。

但是,我发现软件的一个非常重要的方面是可读性。您是否尝试过阅读 10 年前的代码?因此,在 C 语言中做对象之类的事情时,我倾向于采用最简单的方法。

问以下问题:

  1. 这是否适用于有截止日期的客户(如果是,请考虑 OOP)?
  2. 我可以使用 OOP(通常代码更少、开发速度更快、可读性更强)吗?
  3. 我可以使用库(现有代码、现有模板)吗?
  4. 我是否受到内存或 CPU(例如 Arduino)的限制?
  5. 还有其他使用 C 的技术原因吗?
  6. 我可以让我的 C 语言非常简单易读吗?
  7. 我的项目真正需要哪些 OOP 功能?

我通常会使用 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
于 2013-08-14T17:22:34.853 回答
0

看看IJG 的实现。他们不仅使用 setjmp/longjmp 进行异常处理,他们还有 vtables 和一切。这是一个编写良好且足够小的库,可以让您获得一个非常好的示例。

于 2009-08-04T05:50:47.767 回答
0

与 Dale 的方法类似,但更多的是 PostgreSQL 如何在内部表示解析树节点、表达式类型等。有默认NodeExpr结构,沿着

typedef struct {
    NodeTag n;
} Node;

其中NodeTag是 unsigned int 的 typedef,并且有一个头文件,其中包含一堆描述所有可能节点类型的常量。节点本身如下所示:

typedef struct {
    NodeTag n = FOO_NODE;
    /* other members go here */
} FooNode;

并且 aFooNode可以Node不受惩罚地转换为 a,因为 C 结构的一个怪癖:如果两个结构具有相同的第一个成员,它们可以相互转换。

是的,这意味着 aFooNode可以转换为 a BarNode,您可能不想这样做。如果你想要正确的运行时类型检查,GObject 是你要走的路,但要准备好在你掌握它的时候讨厌生活。

(注意:记忆中的例子,我已经有一段时间没有研究过 Postgres 内部了。开发人员常见问题解答有更多信息。)

于 2009-08-04T18:01:31.243 回答