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


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


当前回答

是的,这是可能的。

这是纯C语言,没有宏预处理。它具有继承、多态、数据封装(包括私有数据)。它没有等效的受保护限定符,这意味着私有数据在整个继承链中也是私有的。

#include "triangle.h"
#include "rectangle.h"
#include "polygon.h"

#include <stdio.h>

int main()
{
    Triangle tr1= CTriangle->new();
    Rectangle rc1= CRectangle->new();

    tr1->width= rc1->width= 3.2;
    tr1->height= rc1->height= 4.1;

    CPolygon->printArea((Polygon)tr1);

    printf("\n");

    CPolygon->printArea((Polygon)rc1);
}

/*output:
6.56
13.12
*/

其他回答

动物和狗的小例子:你镜像了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编译器上运行应该很容易。

我认为首先要说的是(至少在我看来)C的函数指针实现真的很难使用。我会跳过一大堆的圆环来避免函数指针…

也就是说,我认为其他人说的很好。你有结构,你有模块,而不是foo->方法(a,b,c),你最终用方法(foo,a,b,c)如果你有一个“method”方法,那么你可以用类型前缀它,所以FOO_method(foo,a,b,c),正如其他人所说…通过良好地使用.h文件,您可以获得私有和公共文件,等等。

现在,有一些事情是这个技巧不能给你的。它不会提供私有数据字段。我认为,这与意志力和良好的编码习惯有关……而且,没有一种简单的方法来继承它。

这些至少是简单的部分……其余的,我认为是90/10的情况。10%的收益需要90%的工作……

我建立了一个小图书馆,在那里我尝试了,对我来说,它真的很有效。所以我想和你分享这段经历。

https://github.com/thomasfuhringer/oxygen

使用结构并将其扩展到每个其他子类,可以很容易地实现单继承。对父结构进行简单的强制转换,就可以在所有后代上使用父方法。 只要知道变量指向持有此类对象的结构体,就可以始终将其转换为根类并进行自省。

正如前面提到的,虚拟方法有点棘手。但它们是可行的。为了保持简单,我只是在类描述结构中使用了一个函数数组,每个子类在需要的地方复制和重新填充单独的插槽。

多重继承实现起来相当复杂,并且会对性能产生重大影响。所以我离开了。我确实认为,在相当多的情况下,清晰地模拟现实生活环境是可取和有用的,但在可能90%的情况下,单一继承可以满足需求。而且单一继承很简单,没有成本。

我也不关心类型安全。我认为你不应该依赖编译器来防止你的编程错误。而且无论如何,它只能保护您避免相当小部分的错误。

通常,在面向对象的环境中,您还希望实现引用计数以尽可能地自动化内存管理。因此,我还在“Object”根类中放入了引用计数,并使用了一些功能来封装堆内存的分配和释放。

它非常简单和精简,给了我OO的基本要领,而不强迫我去处理c++这个怪物。而且我保留了留在C领域的灵活性,这使得集成第三方库更加容易。

看看GObject。它应该是C语言中的OO,是您正在寻找的东西的一个实现。如果你真的想要面向对象,那就使用c++或其他面向对象语言。如果您习惯了处理OO语言,那么GObject有时真的很难使用,但是像其他东西一样,您会习惯它的约定和流程。

在Jim Larson 1996年在312节编程午餐研讨会上的演讲中有一个使用C进行继承的例子:高级和低级C。