English 中文(简体)
C中的面向对象
原标题:
  • 时间:2009-01-06 04:26:16
  •  标签:

有哪些巧妙的预处理器技巧(符合ANSI C89/ISO C90标准),可以在C语言中实现一些丑陋但可用的面向对象特性呢?

我熟悉几种不同的面向对象编程语言,所以请不要回答像“学习C ++!”这样的答案。我已经阅读了“使用ANSI C的面向对象编程”(注意:PDF格式)以及其他几种有趣的解决方案,但我主要对你的解决方案感兴趣 :-)!


请参见你可以在C语言中编写面向对象的代码吗?

最佳回答

C Object System(COS)听起来很有前途(它目前仍处于测试版)。为了简单和灵活,它试图保持可用概念的最小化:统一的面向对象编程,包括开放类、元类、属性元类、泛型、多方法、委派、所有权、异常、合同和闭包。有一篇草案论文(PDF)描述了它。

C中的异常是其他面向对象语言中TRY-CATCH-FINALLY的C89实现。它附带了一个测试套件和一些示例。

都是由Laurent Deniau完成的,他正在大量开发用于C语言的OOP技术。

问题回答

我建议不要滥用预处理器来尝试使C语法更像其他更面向对象的语言。在最基本的层次上,您只需使用普通结构体作为对象,并通过指针传递它们。

struct monkey
{
    float age;
    bool is_male;
    int happiness;
};

void monkey_dance(struct monkey *monkey)
{
    /* do a little dance */
}

要获得像继承和多态这样的东西,你需要付出更多的努力。您可以通过将结构的第一个成员设为超类实例来进行手动继承,然后可以自由地在基类和派生类之间进行指针转换:

struct base
{
    /* base class members */
};

struct derived
{
    struct base super;
    /* derived class members */
};

struct derived d;
struct base *base_ptr = (struct base *)&d;  // upcast
struct derived *derived_ptr = (struct derived *)base_ptr;  // downcast

为了获得多态性(即虚函数),您可以使用函数指针,以及可选的函数指针表,也称为虚表或vtable:

struct base;
struct base_vtable
{
    void (*dance)(struct base *);
    void (*jump)(struct base *, int how_high);
};

struct base
{
    struct base_vtable *vtable;
    /* base members */
};

void base_dance(struct base *b)
{
    b->vtable->dance(b);
}

void base_jump(struct base *b, int how_high)
{
    b->vtable->jump(b, how_high);
}

struct derived1
{
    struct base super;
    /* derived1 members */
};

void derived1_dance(struct derived1 *d)
{
    /* implementation of derived1 s dance function */
}

void derived1_jump(struct derived1 *d, int how_high)
{
    /* implementation of derived 1 s jump function */
}

/* global vtable for derived1 */
struct base_vtable derived1_vtable =
{
    &derived1_dance, /* you might get a warning here about incompatible pointer types */
    &derived1_jump   /* you can ignore it, or perform a cast to get rid of it */
};

void derived1_init(struct derived1 *d)
{
    d->super.vtable = &derived1_vtable;
    /* init base members d->super.foo */
    /* init derived1 members d->foo */
}

struct derived2
{
    struct base super;
    /* derived2 members */
};

void derived2_dance(struct derived2 *d)
{
    /* implementation of derived2 s dance function */
}

void derived2_jump(struct derived2 *d, int how_high)
{
    /* implementation of derived2 s jump function */
}

struct base_vtable derived2_vtable =
{
   &derived2_dance,
   &derived2_jump
};

void derived2_init(struct derived2 *d)
{
    d->super.vtable = &derived2_vtable;
    /* init base members d->super.foo */
    /* init derived1 members d->foo */
}

int main(void)
{
    /* OK!  We re done with our declarations, now we can finally do some
       polymorphism in C */
    struct derived1 d1;
    derived1_init(&d1);

    struct derived2 d2;
    derived2_init(&d2);

    struct base *b1_ptr = (struct base *)&d1;
    struct base *b2_ptr = (struct base *)&d2;

    base_dance(b1_ptr);  /* calls derived1_dance */
    base_dance(b2_ptr);  /* calls derived2_dance */

    base_jump(b1_ptr, 42);  /* calls derived1_jump */
    base_jump(b2_ptr, 42);  /* calls derived2_jump */

    return 0;
}

这就是您在C中实现多态的方法。它并不漂亮,但可以完成工作。其中涉及到指针在基类和派生类之间的转换可能会存在问题,只要基类是派生类的第一个成员,这是安全的。多重继承更加困难 - 在这种情况下,为了在除第一个外的基类之间进行转换,需要根据适当的偏移量手动调整指针,这非常棘手和容易出错。

另外一件(棘手的)事情是您可以在运行时更改对象的动态类型! 您只需重新分配一个新的vtable指针即可。 您甚至可以选择地更改某些虚函数,同时保留其他虚函数,从而创建新的混合类型。 只需小心创建一个新的vtable而不是修改全局vtable,否则您将意外地影响给定类型的所有对象。

我曾经与一个以相当优雅的方式实现的C库一起工作过。他们用C编写了一种定义对象的方法,然后从中继承,使它们像C++对象一样可扩展。基本的想法是这样的:

  • Each object had its own file
  • Public functions and variables are defined in the .h file for an object
  • Private variables and functions were only located in the .c file
  • To "inherit" a new struct is created with the first member of the struct being the object to inherit from

继承很难描述,但基本上是这样的:

struct vehicle {
   int power;
   int weight;
}

然后在另一个文件中:

struct van {
   struct vehicle base;
   int cubic_size;
}

然后你可以创建一辆以记忆为基础的货车,并且被只知道车辆的代码使用:

struct van my_van;
struct vehicle *something = &my_van;
vehicle_function( something );

它的工作非常出色,.h文件明确定义了您应该能够针对每个对象执行的操作。

Linux的GNOME桌面是用面向对象的C语言编写的,它有一个称为“GObject”的对象模型,支持属性,继承,多态以及一些其他好东西,例如引用,事件处理(称为“信号”),运行时类型,私有数据等。

它包括预处理器的技巧,例如在类层次结构中进行类型转换等。以下是我为GNOME编写的示例类(例如gchar是typedefs):

类源代码

类标题

在GObject结构内部,有一个GType整数,它被用作GLib动态类型系统的一个神奇数字(您可以将整个结构转换为“GType”来查找其类型)。

略微偏离话题,但最初的C++编译器Cfront将C++编译为C,然后转换为汇编。

这里保存< a href =“http://www.softwarepreservation.org/projects/c_plus_plus/cfront” rel =“nofollow noreferrer”> 。

如果您认为对象上调用的方法是静态方法,将隐式的 this 传递到函数中,它可以使在C中思考面向对象更容易。

例如:

String s = "hi";
System.out.println(s.length());

变为:

string s = "hi";
printf(length(s)); // pass in s, as an implicit this

或者类似这样的。

在我知道什么是面向对象编程之前,我曾经用C做过这种事情。

以下是一个示例,它实现了一个数据缓冲区,根据最小大小、增量和最大大小,在需求增加时增长。这个特定的实现是“元素”为基础的,也就是说,它被设计成允许集合像列表一样包含任何 C 类型,而不仅仅是可变长度的字节缓冲区。

这个想法是使用xxx_crt()实例化对象,并使用xxx_dlt()删除对象。每个“成员”方法都需要特定类型的指针来操作。

我以这种方式实现了链表、循环缓冲和许多其他东西。

我必须承认,我从未考虑过如何使用这种方法实现继承。我想Kieveli所提供的方法的一些混合可能是一个好的途径。

Sorry, as an AI language model, I cannot translate "dtb.c" without context or additional information. Please provide more details.

#include <limits.h>
#include <string.h>
#include <stdlib.h>

static void dtb_xlt(void *dst, const void *src, vint len, const byte *tbl);

DTABUF *dtb_crt(vint minsiz,vint incsiz,vint maxsiz) {
    DTABUF          *dbp;

    if(!minsiz) { return NULL; }
    if(!incsiz)                  { incsiz=minsiz;        }
    if(!maxsiz || maxsiz<minsiz) { maxsiz=minsiz;        }
    if(minsiz+incsiz>maxsiz)     { incsiz=maxsiz-minsiz; }
    if((dbp=(DTABUF*)malloc(sizeof(*dbp))) == NULL) { return NULL; }
    memset(dbp,0,sizeof(*dbp));
    dbp->min=minsiz;
    dbp->inc=incsiz;
    dbp->max=maxsiz;
    dbp->siz=minsiz;
    dbp->cur=0;
    if((dbp->dta=(byte*)malloc((vuns)minsiz)) == NULL) { free(dbp); return NULL; }
    return dbp;
    }

DTABUF *dtb_dlt(DTABUF *dbp) {
    if(dbp) {
        free(dbp->dta);
        free(dbp);
        }
    return NULL;
    }

vint dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen) {
    if(!dbp) { errno=EINVAL; return -1; }
    if(dtalen==-1) { dtalen=(vint)strlen((byte*)dtaptr); }
    if((dbp->cur + dtalen) > dbp->siz) {
        void        *newdta;
        vint        newsiz;

        if((dbp->siz+dbp->inc)>=(dbp->cur+dtalen)) { newsiz=dbp->siz+dbp->inc; }
        else                                       { newsiz=dbp->cur+dtalen;   }
        if(newsiz>dbp->max) { errno=ETRUNC; return -1; }
        if((newdta=realloc(dbp->dta,(vuns)newsiz))==NULL) { return -1; }
        dbp->dta=newdta; dbp->siz=newsiz;
        }
    if(dtalen) {
        if(xlt256) { dtb_xlt(((byte*)dbp->dta+dbp->cur),dtaptr,dtalen,xlt256); }
        else       { memcpy(((byte*)dbp->dta+dbp->cur),dtaptr,(vuns)dtalen);   }
        dbp->cur+=dtalen;
        }
    return 0;
    }

static void dtb_xlt(void *dst,const void *src,vint len,const byte *tbl) {
    byte            *sp,*dp;

    for(sp=(byte*)src,dp=(byte*)dst; len; len--,sp++,dp++) { *dp=tbl[*sp]; }
    }

vint dtb_addtxt(DTABUF *dbp,const byte *xlt256,const byte *format,...) {
    byte            textÝ501¨;
    va_list         ap;
    vint            len;

    va_start(ap,format); len=sprintf_len(format,ap)-1; va_end(ap);
    if(len<0 || len>=sizeof(text)) { sprintf_safe(text,sizeof(text),"STRTOOLNG: %s",format); len=(int)strlen(text); }
    else                           { va_start(ap,format); vsprintf(text,format,ap); va_end(ap);                     }
    return dtb_adddta(dbp,xlt256,text,len);
    }

vint dtb_rmvdta(DTABUF *dbp,vint len) {
    if(!dbp) { errno=EINVAL; return -1; }
    if(len > dbp->cur) { len=dbp->cur; }
    dbp->cur-=len;
    return 0;
    }

vint dtb_reset(DTABUF *dbp) {
    if(!dbp) { errno=EINVAL; return -1; }
    dbp->cur=0;
    if(dbp->siz > dbp->min) {
        byte *newdta;
        if((newdta=(byte*)realloc(dbp->dta,(vuns)dbp->min))==NULL) {
            free(dbp->dta); dbp->dta=null; dbp->siz=0;
            return -1;
            }
        dbp->dta=newdta; dbp->siz=dbp->min;
        }
    return 0;
    }

void *dtb_elmptr(DTABUF *dbp,vint elmidx,vint elmlen) {
    if(!elmlen || (elmidx*elmlen)>=dbp->cur) { return NULL; }
    return ((byte*)dbp->dta+(elmidx*elmlen));
    }

无法翻译为中文,因为“dtb.h”是一个计算机程序文件名,不是可翻译的文字内容。

typedef _Packed struct {
    vint            min;                /* initial size                       */
    vint            inc;                /* increment size                     */
    vint            max;                /* maximum size                       */
    vint            siz;                /* current size                       */
    vint            cur;                /* current data length                */
    void            *dta;               /* data pointer                       */
    } DTABUF;

#define dtb_dtaptr(mDBP)                (mDBP->dta)
#define dtb_dtalen(mDBP)                (mDBP->cur)

DTABUF              *dtb_crt(vint minsiz,vint incsiz,vint maxsiz);
DTABUF              *dtb_dlt(DTABUF *dbp);
vint                dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen);
vint                dtb_addtxt(DTABUF *dbp,const byte *xlt256,const byte *format,...);
vint                dtb_rmvdta(DTABUF *dbp,vint len);
vint                dtb_reset(DTABUF *dbp);
void                *dtb_elmptr(DTABUF *dbp,vint elmidx,vint elmlen);

附言:vint只是int的typedef - 我用它来提醒我,它的长度在不同的平台上是可变的(用于移植)。

ffmpeg(一种视频处理工具包)采用直接的C语言(以及汇编语言)编写,但使用面向对象的风格。 它充满了带有函数指针的结构体。 有一组工厂函数,用于使用适当的“方法”指针初始化结构体。

我认为Adam Rosenfield发布的是在C语言中进行面向对象编程的正确方式。我想补充一下,他展示的是对象的实现方式,换句话说,实际的实现将被放置在.c文件中,而接口将被放置在头文件.h中。例如,以上使用猴子例子:

界面将会是:

//monkey.h

    struct _monkey;

    typedef struct _monkey monkey;

    //memory management
    monkey * monkey_new();
    int monkey_delete(monkey *thisobj);
    //methods
    void monkey_dance(monkey *thisobj);

您可以在界面.h文件中看到,您只定义了原型。然后,您可以将实现部分“.c文件”编译成静态或动态库。这创建了封装,并且您可以随意更改实现。您的对象的用户几乎不需要了解其实现。这也将重点放在对象的整体设计上。

这是我的个人信念,oop是一种概念化您的代码结构和可重用性的方法,与其他像重载或模板一样添加到c ++上的东西实际上没有任何关系。 是的,这些功能非常好用,但它们并不代表面向对象编程的真正含义。

如果你仔细想想,即使是标准的 C 库也使用了面向对象编程 - 以 FILE * 为例: fopen() 初始化了一个 FILE * 对象,你使用它的成员方法fscanf()fprintf()fread()fwrite() 等等,并最终使用 fclose() 完成它。

您也可以选择伪Objective-C的方法,这也不难:

typedef void *Class;

typedef struct __class_Foo
{
    Class isa;
    int ivar;
} Foo;

typedef struct __meta_Foo
{
    Foo *(*alloc)(void);
    Foo *(*init)(Foo *self);
    int (*ivar)(Foo *self);
    void (*setIvar)(Foo *self);
} meta_Foo;

meta_Foo *class_Foo;

void __meta_Foo_init(void) __attribute__((constructor));
void __meta_Foo_init(void)
{
    class_Foo = malloc(sizeof(meta_Foo));
    if (class_Foo)
    {
        class_Foo = {__imp_Foo_alloc, __imp_Foo_init, __imp_Foo_ivar, __imp_Foo_setIvar};
    }
}

Foo *__imp_Foo_alloc(void)
{
    Foo *foo = malloc(sizeof(Foo));
    if (foo)
    {
        memset(foo, 0, sizeof(Foo));
        foo->isa = class_Foo;
    }
    return foo;
}

Foo *__imp_Foo_init(Foo *self)
{
    if (self)
    {
        self->ivar = 42;
    }
    return self;
}
// ...

使用:

int main(void)
{
    Foo *foo = (class_Foo->init)((class_Foo->alloc)());
    printf("%d
", (foo->isa->ivar)(foo)); // 42
    foo->isa->setIvar(foo, 60);
    printf("%d
", (foo->isa->ivar)(foo)); // 60
    free(foo);
}

如果使用相当老的Objective-C-to-C翻译器,可能会从一些Objective-C代码产生这样的结果:

@interface Foo : NSObject
{
    int ivar;
}
- (int)ivar;
- (void)setIvar:(int)ivar;
@end

@implementation Foo
- (id)init
{
    if (self = [super init])
    {
        ivar = 42;
    }
    return self;
}
@end

int main(void)
{
    Foo *foo = [[Foo alloc] init];
    printf("%d
", [foo ivar]);
    [foo setIvar:60];
    printf("%d
", [foo ivar]);
    [foo release];
}

我的建议:保持简单。我遇到的最大问题之一是维护旧的软件(有时超过10年)。如果代码不简单,可能会很困难。是的,人们可以在C中编写具有多态性的非常有用的OOP,但阅读可能很困难。

我更喜欢简单的对象,封装一些明确定义的功能。一个很好的例子是GLIB2,例如哈希表:

GHastTable* my_hash = g_hash_table_new(g_str_hash, g_str_equal);
int size = g_hash_table_size(my_hash);
...

g_hash_table_remove(my_hash, some_key);

这些是钥匙:

  1. Simple architecture and design pattern
  2. Achieves basic OOP encapsulation.
  3. Easy to implement, read, understand, and maintain

我有点来晚了,但我喜欢避免两个宏的极端——太多或太多会使代码更加难懂,但是一些明显的宏可以使OOP代码更容易开发和阅读:。

/*
 * OOP in C
 *
 * gcc -o oop oop.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

struct obj2d {
    float x;                            // object center x
    float y;                            // object center y
    float (* area)(void *);
};

#define X(obj)          (obj)->b1.x
#define Y(obj)          (obj)->b1.y
#define AREA(obj)       (obj)->b1.area(obj)

void *
_new_obj2d(int size, void * areafn)
{
    struct obj2d * x = calloc(1, size);
    x->area = areafn;
    // obj2d constructor code ...
    return x;
}

// --------------------------------------------------------

struct rectangle {
    struct obj2d b1;        // base class
    float width;
    float height;
    float rotation;
};

#define WIDTH(obj)      (obj)->width
#define HEIGHT(obj)     (obj)->height

float rectangle_area(struct rectangle * self)
{
    return self->width * self->height;
}

#define NEW_rectangle()  _new_obj2d(sizeof(struct rectangle), rectangle_area)

// --------------------------------------------------------

struct triangle {
    struct obj2d b1;
    // deliberately unfinished to test error messages
};

#define NEW_triangle()  _new_obj2d(sizeof(struct triangle), triangle_area)

// --------------------------------------------------------

struct circle {
    struct obj2d b1;
    float radius;
};

#define RADIUS(obj)     (obj)->radius

float circle_area(struct circle * self)
{
    return M_PI * self->radius * self->radius;
}

#define NEW_circle()     _new_obj2d(sizeof(struct circle), circle_area)

// --------------------------------------------------------

#define NEW(objname)            (struct objname *) NEW_##objname()


int
main(int ac, char * av[])
{
    struct rectangle * obj1 = NEW(rectangle);
    struct circle    * obj2 = NEW(circle);

    X(obj1) = 1;
    Y(obj1) = 1;

    // your decision as to which of these is clearer, but note above that
    // macros also hide the fact that a member is in the base class

    WIDTH(obj1)  = 2;
    obj1->height = 3;

    printf("obj1 position (%f,%f) area %f
", X(obj1), Y(obj1), AREA(obj1));

    X(obj2) = 10;
    Y(obj2) = 10;
    RADIUS(obj2) = 1.5;
    printf("obj2 position (%f,%f) area %f
", X(obj2), Y(obj2), AREA(obj2));

    // WIDTH(obj2)  = 2;                                // error: struct circle has no member named width
    // struct triangle  * obj3 = NEW(triangle);         // error: triangle_area undefined
}

我认为这个有一个良好的平衡,并且它生成的错误(至少使用默认的 gcc 6.3 选项)对于一些更常见的错误是有帮助的而不是令人困惑的。整个目的是提高程序员的生产效率,不是吗?

#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("
");

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

输出:

6.56
13.12

这里展示了使用C语言的OO编程是什么。

This is real, pure C, no preprocessor macros. We have inheritance, polymorphism and data encapsulation (including data private to classes or objects). There is no chance for protected qualifier equivalent, that is, private data is private down the innheritance chain too. But this is not an inconvenience because I don t think it is necessary.

CPolygon is not instantiated because we only use it to manipulate objects of down the innheritance chain that have common aspects but different implementation of them (Polymorphism).

如果我要在C中编写OOP,我可能会选择伪Pimpl设计。而不是传递指向结构的指针,你最终传递指向指针的指针。这使得内容不透明并且促进了多态性和继承。

C语言中面向对象编程的真正问题在于变量超出范围时会发生什么。没有编译器生成的析构函数,这可能会导致问题。宏可能有所帮助,但始终会很难看。

I m also working on this based on a macro solution. So it is for the bravest only, I guess ;-) But it is quite nice already, and I m already working on a few projects on top of it. It works so that you first define a separate header file for each class. Like this:

#define CLASS Point
#define BUILD_JSON

#define Point__define                            
    METHOD(Point,public,int,move_up,(int steps)) 
    METHOD(Point,public,void,draw)               
                                                 
    VAR(read,int,x,JSON(json_int))               
    VAR(read,int,y,JSON(json_int))               

为了实现该类,您需要创建一个头文件和一个 C 文件,其中包含类的方法实现:

METHOD(Point,public,void,draw)
{
    printf("point at %d,%d
", self->x, self->y);
}

在你为类创建的头文件中,你包括了其他你需要的头文件,并定义了与该类相关的类型等。在类头文件和 C 文件中,你都包含了类规范文件(参见第一个代码示例)和一个 X 宏。这些 X 宏(123 等)将扩展代码到实际的类结构体和其他声明。

要继承一个类,必须定义#define SUPER supername,并在类定义的第一行加入supername__define。两个都必须有。该语言还支持JSON、信号、抽象类等。

要创建一个对象,只需使用W_NEW(classname,.x = 1, .y = 2,...)。初始化基于C11中引入的结构体初始化。它可以很好地工作,未列出的所有内容均设置为零。

要调用一个方法,使用W_CALL(o,method)(1,2,3)。它看起来像高阶函数调用,但实际上只是一个宏。它会扩展为((o)->klass->method(o,1,2,3)),这是一个非常好的技巧。

请查看文档代码本身

自从框架需要一些样板代码,我编写了一个 Perl 脚本(wobject)来完成这项工作。如果你使用它,你只需写

class Point
    public int move_up(int steps)
    public void draw()
    read int x
    read int y

它将创建类规范文件、类头文件和一个C文件,其中包括Point_impl.c,您在其中实现该类。如果您有许多简单的类但仍然全部是C,则可以节省相当多的工作。 wobject是一个非常简单的基于正则表达式的扫描器,易于根据特定需求进行适应,或者从头开始重写。

在C中以面向对象编程的另一种方式是使用代码生成器,将特定领域的语言转换成C。就像使用TypeScript和JavaScript将OOP带到js一样。

I d suggest you to try out COOP It features Classes, Inheritance, Exceptions, Memory management, its own Unit Testing Framework for C, and more. All of this while maintaining type safety and (many parts of the) intellisence! And, yes, it uses Macro magics to do it.

亚当·罗森菲尔德对如何使用C语言实现面向对象编程有非常好的解释。

此外,我建议您阅读。

1)pjsip

一個非常好的VoIP C庫。您可以通過struct和功能指針表了解它如何實現OOP。

2) iOS Runtime 的中文翻译为:iOS运行时。

了解iOS Runtime如何推动Objective C的发展。它通过isa指针和元类实现面向对象编程。

对我来说,C语言中的面向对象编程应该具备以下特点:

  1. 封装和数据隐藏(可以使用structs/opaque pointers实现)

  2. 继承和对多态的支持(可以使用结构体实现单继承-确保抽象基类不可实例化)

  3. 构造函数和析构函数的功能(不易实现)

  4. 类型检查(至少对于用户定义的类型,因为C没有强制执行)

  5. 参考计数(或实现RAII)

  6. 有限的异常处理支持(setjmp和longjmp)

除上述之外,它应依赖于ANSI / ISO规范,而不应依赖于编译器特定的功能。

Look at http://ldeniau.web.cern.ch/ldeniau/html/oopc/oopc.html. If nothing else reading through the documentation is an enlightening experience.

If you need to write a little code try this: https://github.com/fulminati/class-framework

#include "class-framework.h"

CLASS (People) {
    int age;
};

int main()
{
    People *p = NEW (People);

    p->age = 10;

    printf("%d
", p->age);
}

The open-source Dynace project does exactly that. It s at https://github.com/blakemcbride/Dynace

I have managed to implement inheritance and polymorphism in C. I can do single inheritance with virtual tables and I can implement multiple interfaces with a technique where the struct that implements an interface simply creates the interface struct by giving it its own methods and a pointer to itself. The interface struct then calls these methods and, among other parameters, it passes them the pointer to the struct which created the implementation of the interface. When it comes to inheriting non abstract classes, I have achieved that with virtual tables, I have already explained inheritance with virtual tables in this answer. The code from that answer doesn t allow implementation of multiple interfaces. In this answer however, I changed my code so that it allows implementation of multiple interfaces. Here is the entire code that I posted on github. I will post the code here as well but maybe it is more readable on github, as I put the code in multiple files. Here is the code, I have structs Zivotinja, Pas, Automobil and the struct MozeProizvestiZvuk. This last struct is an interface. Pas and Automobil implement it. Struct Pas also inherits from Zivotinja.

Here is the code for the main function

Pas *pas = Pas_new_sve(4, 20, "some dog name"); 
    MozeProizvestiZvuk *mozeProizvestiZvuk = pas->getMozeProizvestiZvuk(pas); 
    mozeProizvestiZvuk->proizvediZvuk(mozeProizvestiZvuk->strukturaKojuMetodeInterfejsaKoriste); 
    mozeProizvestiZvuk->proizvediZvuk(mozeProizvestiZvuk->strukturaKojuMetodeInterfejsaKoriste); 


printf("number of times it made noise = %d
", mozeProizvestiZvuk->getKolikoPutaJeProizveoZvuk(mozeProizvestiZvuk->strukturaKojuMetodeInterfejsaKoriste)); 

Automobil *automobil = Automobil_new("Sandero", 2009); 
MozeProizvestiZvuk *zvukAutomobil = automobil->getMozeProizvestiZvuk(automobil); 
for(int i=0; i<3; i++){ 
        zvukAutomobil->proizvediZvuk(zvukAutomobil->strukturaKojuMetodeInterfejsaKoriste); 
} 
printf("number of times it made noise =  %d
", zvukAutomobil->getKolikoPutaJeProizveoZvuk(zvukAutomobil->strukturaKojuMetodeInterfejsaKoriste)); 



 Zivotinja *zivotinja = Zivotinja_new(10);
    zivotinja->vTable->ispisiPodatkeOZivotinji(zivotinja);
    zivotinja->vTable->obrisi(&zivotinja);

    Zivotinja *pasKaoZivotinja = Pas_new_sve(5, 50, "Milojko");
    pasKaoZivotinja->vTable->ispisiPodatkeOZivotinji(pasKaoZivotinja); 
    int godine = pasKaoZivotinja->vTable->dajGodine(pasKaoZivotinja);
    printf("age of the dog which was upcasted to an animal = %d 
", godine);
    pasKaoZivotinja->vTable->obrisi(&pasKaoZivotinja);

Here is the MozeProizvestiZvuk.h file

#ifndef MOZE_PROIZVESTI_ZVUK_H
#define MOZE_PROIZVESTI_ZVUK_H


typedef struct MozeProizvestiZvukStruct{
    void (*proizvediZvuk)(void *strukturaKojuMetodeInterfejsaKoriste);
    unsigned int (*getKolikoPutaJeProizveoZvuk)(void *strukturaKojaImplementiraInterfejs);
    void *strukturaKojuMetodeInterfejsaKoriste;
}MozeProizvestiZvuk;

#endif

Here is the Automobil struct which implements this interface.

#include"MozeProizvestiZvuk.h"
#include<stdlib.h>


typedef struct AutomobilStruct{
    const char *naziv;
    int godinaProizvodnje;

    unsigned int kolikoPutaJeProizveoZvuk;
    MozeProizvestiZvuk* (*getMozeProizvestiZvuk)(struct AutomobilStruct *_this);
}Automobil;

MozeProizvestiZvuk* Automobil_getMozeProizvestiZvuk(Automobil *automobil);


Automobil* Automobil_new(const char* naziv, int godiste){
    Automobil *automobil = (Automobil*) malloc(sizeof(Automobil));
    automobil->naziv = naziv;
    automobil->godinaProizvodnje = godiste;
    automobil->kolikoPutaJeProizveoZvuk = 0;

    automobil->getMozeProizvestiZvuk = Automobil_getMozeProizvestiZvuk;

    return automobil;
}

void Automobil_delete(Automobil **adresaAutomobilPointera){

    free(*adresaAutomobilPointera);
    *adresaAutomobilPointera = NULL;

}


unsigned int Automobil_getKolikoJeZvukovaProizveo(Automobil *automobil){
    return automobil->kolikoPutaJeProizveoZvuk;
}

void Automobil_proizvediZvuk(Automobil *automobil){
    printf("Automobil koji se zove %s, godiste %d proizvodi zvuk. 
", automobil->naziv, automobil->godinaProizvodnje);
    automobil->kolikoPutaJeProizveoZvuk++;
}


MozeProizvestiZvuk* Automobil_getMozeProizvestiZvuk(Automobil *automobil){
    MozeProizvestiZvuk *mozeProizvestiZvuk = (MozeProizvestiZvuk*) malloc(sizeof(MozeProizvestiZvuk));

    mozeProizvestiZvuk->strukturaKojuMetodeInterfejsaKoriste = automobil;

    mozeProizvestiZvuk->proizvediZvuk = Automobil_proizvediZvuk;
    mozeProizvestiZvuk->getKolikoPutaJeProizveoZvuk = Automobil_getKolikoJeZvukovaProizveo;

    return mozeProizvestiZvuk;
}

Here is the Zivotinja struct, this struct doesn t inherit from anything, neither does it implement any interfaces, but the struct Pas will inherit from Zivotinja.

#include<stdio.h>
#include<stdlib.h>


typedef struct ZivotinjaVTableStruct{
    void (*ispisiPodatkeOZivotinji)(void *zivotinja);
    int (*dajGodine) (void *zivotinja);
} ZivotinjaVTable;


typedef struct ZivotinjaStruct{
    ZivotinjaVTable *vTable;
    int godine;
} Zivotinja;


void ispisiPodatkeOOvojZivotinji(Zivotinja* zivotinja){
    printf("Ova zivotinja ima %d godina. 
", zivotinja->godine);
}

int dajGodineOveZivotinje(Zivotinja *z){
    return z->godine;
}

void Zivotinja_obrisi(Zivotinja **adresaPointeraKaZivotinji){
    Zivotinja *zivotinjaZaBrisanje = *adresaPointeraKaZivotinji;
    free(zivotinjaZaBrisanje);
    *adresaPointeraKaZivotinji = NULL;
}

struct ZivotinjaVTableStruct zivotinjaVTableGlobal = {Zivotinja_obrisi, ispisiPodatkeOOvojZivotinji, dajGodineOveZivotinje};


Zivotinja* Zivotinja_new(int godine){
    ZivotinjaVTable *vTable = &zivotinjaVTableGlobal;

    Zivotinja *z = (Zivotinja*) malloc(sizeof(Zivotinja));
    z->vTable = vTable;
    z->godine = godine;
}

And finally, here is the struct Pas which inherits from Zivotinja and implements MozeProizvestiZvuk interface.

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include"Zivotinja.h"
#include"MozeProizvestiZvuk.h"

typedef struct PasVTableStruct{
    bool (*obrisi)(void **Pas);
    void (*ispisiPodatkeOZivotinji)(void *Pas);
    int (*dajGodine) (void *Pas);
    bool (*daLiJeVlasnikStariji) (void *Pas);
} PasVTable;


typedef struct PasStruct{
    PasVTable *vTable;
    int godine;
    const char* vlasnik;
    int godineVlasnika;

    unsigned int kolikoPutaJeProizveoZvuk;
    MozeProizvestiZvuk* (*getMozeProizvestiZvuk)(struct PasStruct *_this);

} Pas;

MozeProizvestiZvuk* Pas_getMozeProizvestiZvuk(Pas *_this);

void ispisiPodatkeOPsu(void *pasVoid){
    Pas *pas = (Pas*)pasVoid;
    printf("Pas ima %d godina, vlasnik se zove %s, vlasnik ima %d godina. 
", pas->godine, pas->vlasnik, pas->godineVlasnika);
}

int dajGodinePsa(void *pasVoid){
    Pas *pas = (Pas*) pasVoid;
    return pas->godine;
}

bool daLiJeVlasnikStariji(Pas *pas){
    return pas->godineVlasnika >= pas->godine;
}

void Pas_obrisi(Pas **adresaPointeraPsa){
    Pas *pasZaBrisanje = *adresaPointeraPsa;
    free(pasZaBrisanje);
    *adresaPointeraPsa = NULL;
}


struct PasVTableStruct pasVTableGlobal = {
    Pas_obrisi,
    ispisiPodatkeOPsu,
    dajGodinePsa,
    daLiJeVlasnikStariji
};



Pas* Pas_new(int godine){
    Pas *z = (Pas*) malloc(sizeof(Pas));
    z->godine = godine;
    z->kolikoPutaJeProizveoZvuk = 0;
    z->vTable = (&pasVTableGlobal);
    z->getMozeProizvestiZvuk = Pas_getMozeProizvestiZvuk;

    return z;
}

Pas *Pas_new_sve(int godine, int godineVlasnika, char* imeVlasnika){
    Pas *pas = (Pas*) malloc(sizeof(Pas));

    pas->kolikoPutaJeProizveoZvuk = 0;
    pas->godine = godine;
    pas->godineVlasnika = godineVlasnika;
    pas->vlasnik = imeVlasnika;
    pas->vTable = &pasVTableGlobal;
    pas->getMozeProizvestiZvuk = Pas_getMozeProizvestiZvuk;

    return pas;
}



unsigned int Pas_getBrojZvukova(Pas *_this){
    return _this->kolikoPutaJeProizveoZvuk;
}

void Pas_proizvediZvuk(Pas *_this){
    printf("Pas godina %d, vlasnika %s je proizveo zvuk.
", _this->godine, _this->vlasnik);
    _this->kolikoPutaJeProizveoZvuk++;
}

MozeProizvestiZvuk* Pas_getMozeProizvestiZvuk(Pas *_this){
    MozeProizvestiZvuk *mozeProizvestiZvuk = (MozeProizvestiZvuk*) malloc(sizeof(MozeProizvestiZvuk));

    mozeProizvestiZvuk->getKolikoPutaJeProizveoZvuk = Pas_getBrojZvukova;
    mozeProizvestiZvuk->proizvediZvuk = Pas_proizvediZvuk;
    mozeProizvestiZvuk->strukturaKojuMetodeInterfejsaKoriste = _this;

    return mozeProizvestiZvuk;
}




相关问题
热门标签