如何在C中模拟OO风格的多态?

pri*_*bel 68 c oop polymorphism

有没有办法在C编程语言中编写类似OO的代码?


也可以看看:

通过搜索"[c] oo"找到.

Unc*_*eiv 58

第一个C++编译器("C with classes")实际上会生成C代码,所以这绝对可行.

基本上,你的基类是一个结构; 派生结构必须在第一个位置包含基本结构,因此指向"derived"结构的指针也将是指向基本结构的有效指针.

typedef struct {
   data member_x;
} base;

typedef struct {
   struct base;
   data member_y;
} derived;

void function_on_base(struct base * a); // here I can pass both pointers to derived and to base

void function_on_derived(struct derived * b); // here I must pass a pointer to the derived class
Run Code Online (Sandbox Code Playgroud)

这些函数可以作为函数指针的结构的一部分,因此像p-> call(p)这样的语法变得可能,但你仍然必须显式地将指向结构的指针传递给函数本身.

  • 这个答案是不正确的.将`struct derived*`传递给`function_on_base`将无法编译; `struct derived*`是一个不同于`struct base*`的类型,即使地址是正确的; 但是,如果你将指针从`derived*`转换为`base*`,它将起作用(但你会错过编译时类型检查,而是在运行时崩溃).@PatrickCollins在C中覆盖_is_:http://pastebin.com/W5xEytbv (7认同)
  • 这没有解释方法重写如何在C中工作.如何在C++多态调用中覆盖function_on_base来访问派生的memeber_y? (6认同)

Pet*_*aný 51

常见的方法是使用指向函数的指针定义struct.这定义了可以在任何类型上调用的"方法".子类型然后在这个公共结构中设置它们自己的函数,并返回它.

例如,在linux内核中,有struct:

struct inode_operations {
    int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
    struct dentry * (*lookup) (struct inode *,struct dentry *, 
                               struct nameidata *);
    ...
};
Run Code Online (Sandbox Code Playgroud)

每个注册的类型的文件系统的随后登记其自己的功能create,lookup和其余功能.其余代码可以使用泛型inode_operations:

struct inode_operations   *i_op;
i_op -> create(...);
Run Code Online (Sandbox Code Playgroud)

  • 听起来很有趣 :-) (4认同)

thi*_*ing 29

C++离C不远.

类是具有指向名为VTable的函数指针表的隐藏指针的结构.Vtable本身是静态的.当类型指向具有相同结构的Vtables但指针指向其他实现时,您将获得多态性.

建议将调用逻辑封装在以struct为参数的函数中,以避免代码混乱.

您还应该在函数中封装结构实例化和初始化(这相当于C++构造函数)和删除(C++中的析构函数).无论如何这些都是很好的做法.

typedef struct
{
   int (*SomeFunction)(TheClass* this, int i);
   void (*OtherFunction)(TheClass* this, char* c);
} VTable;

typedef struct
{
   VTable* pVTable;
   int member;

} TheClass;
Run Code Online (Sandbox Code Playgroud)

要调用方法:

int CallSomeFunction(TheClass* this, int i)
{
  (this->pVTable->SomeFunction)(this, i);
}
Run Code Online (Sandbox Code Playgroud)


小智 15

我查看了所有人的答案并提出了这个问题:

#include <stdio.h>

typedef struct
{
    int (*get)(void* this);
    void (*set)(void* this, int i);
    int member;

} TheClass;

int Get(void* this)
{
    TheClass* This = (TheClass*)this;
    return This->member;
}

void Set(void* this, int i)
{
    TheClass* This = (TheClass*)this;
    This->member = i;
}

void init(TheClass* this)
{
    this->get = &Get;
    this->set = &Set;
}

int main(int argc, char **argv)
{
    TheClass name;
    init(&name);
    (name.set)(&name, 10);
    printf("%d\n", (name.get)(&name));
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我希望能回答一些问题.

  • 好例子.如果你有两个不同的init/get/set的"派生"类会更好."私人"成员/功能可以使用[不透明结构](http://stackoverflow.com/questions/3965279/opaque-c-structs-how-should-they-be-declared)完成.命名约定也很重要:`mylib_someClass_aMethod(this)`是一个很好的可能性. (3认同)

Séb*_*rra 12

VPRI的Ian Piumarta和Alessandro Warth 的文章开放可重用对象模型的附录B是GNU C中的对象模型的实现,大约140行代码.这是一个迷人的读物!

这是使用GNU扩展到C(语句表达式)的向对象发送消息的宏的未缓存版本:

struct object;

typedef struct object *oop; 
typedef oop *(*method_t)(oop receiver, ...);

//...

#define send(RCV, MSG, ARGS...) ({ \ 
    oop r = (oop)(RCV); \ 
    method_t method = _bind(r, (MSG)); \ 
    method(r, ##ARGS); \ 
}) 
Run Code Online (Sandbox Code Playgroud)

在同一个文档,有一个看看object,vtable,vtable_delegatedsymbol结构,以及_bindvtable_lookup功能.

干杯!