人们使用什么技术/策略来构建C(而不是C ++)中的对象?

时间:2009-08-04 05:48:28

标签: c inheritance object c99 c89

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

6 个答案:

答案 0 :(得分: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所提到的),但我建议确保你真的需要更复杂框架的额外功能。

答案 1 :(得分:7)

你对术语“对象”的使用有点模糊,所以我假设你正在问如何使用C来实现面向对象编程的某些方面(在这个假设下随意纠正我。 )

Method Polymorphism:

方法多态性通常使用函数指针在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来使用它的缩放方法。

<强>继承

继承通常是这样实现的:

struct base{
   int x;
   int y;
} 

struct derived{
   struct base;
   int z;
}

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

答案 2 :(得分:4)

GObject等图书馆。

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

您通常也会像"COM in plain C"

那样手工实施vtable

答案 3 :(得分:3)

从浏览所有答案可以看出,有图书馆, 函数指针,继承手段,封装等等 可用(C ++最初是C的前端)。

但是,我发现软件的一个非常重要的方面是 可读性。您是否尝试过阅读10年前的代码?作为一个 结果,我倾向于采取最简单的方法来做这样的事情 C.中的对象。

提出以下问题:

  1. 这是针对有截止日期的客户(如果是,请考虑OOP)?
  2. 我可以使用OOP(通常代码更少,开发更快,更易读)吗?
  3. 我可以使用库(现有代码,现有模板)吗?
  4. 我是否被内存或CPU限制(例如Arduino)?
  5. 使用C还有其他技术原因吗?
  6. 我可以保持我的C非常简单易读吗?
  7. 我的项目真正需要哪些OOP功能?
  8. 我通常会回复到允许我这样的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
    

答案 4 :(得分:0)

查看IJG's实施情况。他们不仅使用setjmp / longjmp进行异常处理,还拥有vtable和一切。这是一个写得很好,足够小的库,可以让你得到一个很好的例子。

答案 5 :(得分:0)

与Dale的方法类似,但是更多的一个步枪是PostgreSQL如何在内部表示解析树节点,表达式类型等。根据

行,有默认的NodeExpr结构
typedef struct {
    NodeTag n;
} Node;

其中NodeTag是unsigned int的typedef,并且有一个头文件,其中包含一堆描述所有可能节点类型的常量。节点本身看起来像这样:

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

并且FooNode可以被投射到Node而不受惩罚,因为C结构的怪癖:如果两个结构具有相同的第一个成员,则它们可以相互投射。

是的,这意味着FooNode可以投射到BarNode,您可能不想这样做。如果你想要正确的运行时类型检查,那么GObject就是你要走的路,尽管你准备好在生活中憎恨生活。

(注意:记忆中的例子,我有一段时间没有攻击Postgres内部。developer FAQ有更多信息。)