用C编写面向对象代码有哪些方法?特别是在多态性方面。


另请参阅此堆栈溢出问题C中的面向对象。


当前回答

当然这是可能的。这就是所有GTK+和GNOME所基于的框架GObject所做的工作。

其他回答

OOP只是一个范例,在程序中数据比代码更重要。OOP不是一种语言。因此,就像普通C是一种简单的语言一样,普通C中的OOP也很简单。

是的,但我从未见过有人尝试用C实现任何类型的多态性。

既然你说的是多态性,那么是的,你可以,我们在c++出现之前几年就已经在做这类事情了。

基本上,你使用一个结构体来保存数据和一个函数指针列表,以指向该数据的相关函数。

因此,在一个通信类中,你会有一个打开、读、写和关闭调用,它将被维护为结构中的四个函数指针,与对象的数据一起,类似于:

typedef struct {
    int (*open)(void *self, char *fspec);
    int (*close)(void *self);
    int (*read)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    int (*write)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    // And data goes here.
} tCommClass;

tCommClass commRs232;
commRs232.open = &rs232Open;
: :
commRs232.write = &rs232Write;

tCommClass commTcp;
commTcp.open = &tcpOpen;
: :
commTcp.write = &tcpWrite;

当然,上面的那些代码段实际上是在诸如rs232Init()这样的“构造函数”中。

当你“继承”这个类时,你只需要改变指针指向你自己的函数。每个调用这些函数的人都会通过函数指针来做,给你你的多态性:

int stat = (commTcp.open)(commTcp, "bigiron.box.com:5000");

有点像手动虚表。

你甚至可以通过将指针设置为NULL来创建虚拟类——这与c++的行为略有不同(运行时的核心转储而不是编译时的错误)。

下面是一段演示它的示例代码。首先是顶级类结构:

#include <stdio.h>

// The top-level class.

typedef struct sCommClass {
    int (*open)(struct sCommClass *self, char *fspec);
} tCommClass;

然后我们有TCP '子类'的函数:

// Function for the TCP 'class'.

static int tcpOpen (tCommClass *tcp, char *fspec) {
    printf ("Opening TCP: %s\n", fspec);
    return 0;
}
static int tcpInit (tCommClass *tcp) {
    tcp->open = &tcpOpen;
    return 0;
}

HTTP也是一样:

// Function for the HTTP 'class'.

static int httpOpen (tCommClass *http, char *fspec) {
    printf ("Opening HTTP: %s\n", fspec);
    return 0;
}
static int httpInit (tCommClass *http) {
    http->open = &httpOpen;
    return 0;
}

最后是一个测试程序来展示它的作用:

// Test program.

int main (void) {
    int status;
    tCommClass commTcp, commHttp;

    // Same 'base' class but initialised to different sub-classes.

    tcpInit (&commTcp);
    httpInit (&commHttp);

    // Called in exactly the same manner.

    status = (commTcp.open)(&commTcp, "bigiron.box.com:5000");
    status = (commHttp.open)(&commHttp, "http://www.microsoft.com");

    return 0;
}

这将产生输出:

Opening TCP: bigiron.box.com:5000
Opening HTTP: http://www.microsoft.com

你可以看到不同的函数被调用,取决于子类。

命名空间通常通过以下方式实现:

stack_push(thing *)

而不是

stack::push(thing *)

要将C结构体变成类似c++类的东西,您可以转向:

class stack {
     public:
        stack();
        void push(thing *);
        thing * pop();
        static int this_is_here_as_an_example_only;
     private:
        ...
};

Into

struct stack {
     struct stack_type * my_type;
     // Put the stuff that you put after private: here
};
struct stack_type {
     void (* construct)(struct stack * this); // This takes uninitialized memory
     struct stack * (* operator_new)(); // This allocates a new struct, passes it to construct, and then returns it
     void (*push)(struct stack * this, thing * t); // Pushing t onto this stack
     thing * (*pop)(struct stack * this); // Pops the top thing off the stack and returns it
     int this_is_here_as_an_example_only;
}Stack = {
    .construct = stack_construct,
    .operator_new = stack_operator_new,
    .push = stack_push,
    .pop = stack_pop
};
// All of these functions are assumed to be defined somewhere else

和做的事:

struct stack * st = Stack.operator_new(); // Make a new stack
if (!st) {
   // Do something about it
} else {
   // You can use the stack
   stack_push(st, thing0); // This is a non-virtual call
   Stack.push(st, thing1); // This is like casting *st to a Stack (which it already is) and doing the push
   st->my_type.push(st, thing2); // This is a virtual call
}

我没有做析构函数或删除,但它遵循相同的模式。

This_is_here_as_an_example_only类似于一个静态类变量——在一个类型的所有实例之间共享。所有的方法都是静态的,除了一些采用this *

动物和狗的小例子:你镜像了c++的虚表机制(基本上是这样)。你还分离了分配和实例化(Animal_Alloc, Animal_New),所以我们不会多次调用malloc()。我们还必须显式地传递this指针。

如果你要做非虚函数,那就很简单了。你只是不需要将它们添加到虚函数表中,静态函数也不需要this指针。多重继承通常需要多个虚表来解决歧义。

此外,您应该能够使用setjmp/longjmp来进行异常处理。

struct Animal_Vtable{
    typedef void (*Walk_Fun)(struct Animal *a_This);
    typedef struct Animal * (*Dtor_Fun)(struct Animal *a_This);

    Walk_Fun Walk;
    Dtor_Fun Dtor;
};

struct Animal{
    Animal_Vtable vtable;

    char *Name;
};

struct Dog{
    Animal_Vtable vtable;

    char *Name; // Mirror member variables for easy access
    char *Type;
};

void Animal_Walk(struct Animal *a_This){
    printf("Animal (%s) walking\n", a_This->Name);
}

struct Animal* Animal_Dtor(struct Animal *a_This){
    printf("animal::dtor\n");
    return a_This;
}

Animal *Animal_Alloc(){
    return (Animal*)malloc(sizeof(Animal));
}

Animal *Animal_New(Animal *a_Animal){
    a_Animal->vtable.Walk = Animal_Walk;
    a_Animal->vtable.Dtor = Animal_Dtor;
    a_Animal->Name = "Anonymous";
    return a_Animal;
}

void Animal_Free(Animal *a_This){
    a_This->vtable.Dtor(a_This);

    free(a_This);
}

void Dog_Walk(struct Dog *a_This){
    printf("Dog walking %s (%s)\n", a_This->Type, a_This->Name);
}

Dog* Dog_Dtor(struct Dog *a_This){
    // Explicit call to parent destructor
    Animal_Dtor((Animal*)a_This);

    printf("dog::dtor\n");

    return a_This;
}

Dog *Dog_Alloc(){
    return (Dog*)malloc(sizeof(Dog));
}

Dog *Dog_New(Dog *a_Dog){
    // Explict call to parent constructor
    Animal_New((Animal*)a_Dog);

    a_Dog->Type = "Dog type";
    a_Dog->vtable.Walk = (Animal_Vtable::Walk_Fun) Dog_Walk;
    a_Dog->vtable.Dtor = (Animal_Vtable::Dtor_Fun) Dog_Dtor;

    return a_Dog;
}

int main(int argc, char **argv){
    /*
      Base class:

        Animal *a_Animal = Animal_New(Animal_Alloc());
    */
    Animal *a_Animal = (Animal*)Dog_New(Dog_Alloc());

    a_Animal->vtable.Walk(a_Animal);

    Animal_Free(a_Animal);
}

PS.这是在c++编译器上测试的,但是在C编译器上运行应该很容易。