我对打算在 C 中使用的对象特别感兴趣,而不是构成解释语言(如 python)核心的对象实现。
6 回答
我倾向于做这样的事情:
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 所提到的),但我建议确保您确实需要更复杂框架的额外功能。
您对“对象”一词的使用有点含糊,所以我假设您在问如何使用 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 的所有“继承”字段。此外,如果您有一个只接受结构基础的函数。您可以简单地将您的结构派生指针转换为结构基指针,而不会产生任何后果
浏览所有答案可以看到,有库、函数指针、继承方式、封装等,一应俱全(C++原本是C的前端)。
但是,我发现软件的一个非常重要的方面是可读性。您是否尝试过阅读 10 年前的代码?因此,在 C 语言中做对象之类的事情时,我倾向于采用最简单的方法。
问以下问题:
- 这是否适用于有截止日期的客户(如果是,请考虑 OOP)?
- 我可以使用 OOP(通常代码更少、开发速度更快、可读性更强)吗?
- 我可以使用库(现有代码、现有模板)吗?
- 我是否受到内存或 CPU(例如 Arduino)的限制?
- 还有其他使用 C 的技术原因吗?
- 我可以让我的 C 语言非常简单易读吗?
- 我的项目真正需要哪些 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
看看IJG 的实现。他们不仅使用 setjmp/longjmp 进行异常处理,他们还有 vtables 和一切。这是一个编写良好且足够小的库,可以让您获得一个非常好的示例。
与 Dale 的方法类似,但更多的是 PostgreSQL 如何在内部表示解析树节点、表达式类型等。有默认Node
和Expr
结构,沿着
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 内部了。开发人员常见问题解答有更多信息。)