171

什么是一组漂亮的预处理器黑客(ANSI C89/ISO C90 兼容)在 C 中启用某种丑陋(但可用)的面向对象?

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


另请参阅您可以用 C 编写面向对象的代码吗?

4

22 回答 22

199

我建议不要使用预处理器 (ab) 来尝试使 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

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

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,否则你会意外地影响给定类型的所有对象。

于 2009-01-06T05:09:55.010 回答
35

我曾经使用过一个 C 库,该库的实现方式让我觉得非常优雅。他们用 C 语言编写了一种定义对象的方法,然后从它们继承,以便它们像 C++ 对象一样可扩展。基本思想是这样的:

  • 每个对象都有自己的文件
  • 公共函数和变量在对象的 .h 文件中定义
  • 私有变量和函数仅位于 .c 文件中
  • 为了“继承”一个新结构,结构的第一个成员是要继承的对象

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

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 文件准确地定义了您应该能够对每个对象执行的操作。

于 2009-01-06T04:28:55.403 回答
34

C 对象系统 (COS)听起来很有希望(它仍处于 alpha 版本)。为了简单和灵活,它尽量减少可用的概念:统一的面向对象编程,包括开放类、元类、属性元类、泛型、多方法、委托、所有权、异常、合同和闭包。有一份草稿文件(PDF) 对其进行了描述。

C 中的异常是在其他 OO 语言中发现的 TRY-CATCH-FINALLY 的 C89 实现。它带有一个测试套件和一些示例。

两者都由 Laurent Deniau 撰写,他在 C 语言的 OOP 方面做了很多工作。

于 2009-01-06T07:51:10.443 回答
18

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

它包括预处理器黑客来做一些事情,比如在类层次结构中进行类型转换等。这是我为 GNOME 编写的一个示例类(gchar 之类的东西是 typedefs):

类源

类头

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

于 2009-01-06T05:06:40.650 回答
7

有点题外话,但最初的 C++ 编译器Cfront将 C++ 编译为 C,然后编译为汇编程序。

保存在这里

于 2009-08-05T09:55:58.153 回答
6

如果您将在对象上调用的方法视为将隐式 ' ' 传递this给函数的静态方法,则可以使 C 中的 OO 思维变得更容易。

例如:

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

变成:

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

或类似的东西。

于 2009-01-06T05:04:55.380 回答
6

在我知道 OOP 是什么之前,我曾经在 C 中做这种事情。

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

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

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

我必须承认,我从来没有考虑过如何用这种方法实现继承。我想 Kieveli 提供的一些混合可能是一条好路。

dtb.c:

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

PS:vint 只是 int 的 typedef - 我用它来提醒我它的长度因平台而异(用于移植)。

于 2009-01-06T05:14:39.540 回答
5

我认为 Adam Rosenfield 发布的是在 C 中执行 OOP 的正确方法。我想补充一点,他展示的是对象的实现。换句话说,实际的实现将放在.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++ 中的其他内容(如重载或模板)实际上无关。是的,这些都是非常有用的特性,但它们并不代表真正的面向对象编程。

于 2015-01-08T16:04:38.777 回答
4

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

于 2009-01-06T04:31:34.980 回答
3

如果您真的仔细考虑,即使是标准 C 库也使用 OOP -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\n", (foo->isa->ivar)(foo)); // 42
    foo->isa->setIvar(foo, 60);
    printf("%d\n", (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\n", [foo ivar]);
    [foo setIvar:60];
    printf("%d\n", [foo ivar]);
    [foo release];
}
于 2013-06-02T15:56:54.883 回答
3

我的建议:保持简单。我遇到的最大问题之一是维护旧软件(有时超过 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. 简单的架构和设计模式
  2. 实现基本的OOP封装。
  3. 易于实施、阅读、理解和维护
于 2013-10-10T19:59:19.917 回答
2

我在这里聚会有点晚了,但我喜欢避免两个宏极端 - 太多或太多混淆代码,但是几个明显的宏可以使 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\n", X(obj1), Y(obj1), AREA(obj1));

    X(obj2) = 10;
    Y(obj2) = 10;
    RADIUS(obj2) = 1.5;
    printf("obj2 position (%f,%f) area %f\n", 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 选项)对于一些更可能的错误是有帮助的,而不是令人困惑。重点是提高程序员的生产力,不是吗?

于 2017-05-30T01:21:29.030 回答
1

如果我要在 CI 中编写 OOP,可能会采用伪Pimpl设计。您最终将指针传递给指向结构的指针,而不是传递指向结构的指针。这使得内容不透明并促进多态性和继承。

C 中 OOP 的真正问题是当变量退出作用域时会发生什么。没有编译器生成的析构函数,这可能会导致问题。可能会有所帮助,但看起来总是很难看。

于 2009-01-06T08:15:14.230 回答
1
#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);
}

输出:

6.56
13.12

这里展示了什么是用 C 进行的 OO 编程。

这是真正的纯 C,没有预处理器宏。我们有继承、多态和数据封装(包括类或对象私有的数据)。受保护的限定符等价物没有机会,也就是说,私有数据在继承链中也是私有的。但这不是不便,因为我认为没有必要。

CPolygon没有被实例化,因为我们只使用它来操作继承链下游的对象,这些对象具有共同的方面但它们的不同实现(多态性)。

于 2012-07-24T10:10:34.873 回答
1

我也在基于一个宏观解决方案来解决这个问题。所以它只适用于最勇敢的人,我猜 ;-) 但它已经很不错了,而且我已经在它之上进行了一些项目。它的工作原理是,您首先为每个类定义一个单独的头文件。像这样:

#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\n", self->x, self->y);
}

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

继承一个类,#define SUPER supernamesupername__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是一个非常简单的基于正则表达式的扫描器,它很容易适应特定需求,或者从头开始重写。

于 2019-03-07T17:19:09.287 回答
1

使用 C 以面向对象的风格进行编程的另一种方法是使用代码生成器,它将特定领域的语言转换为 C。就像使用 TypeScript 和 JavaScript 将 OOP 引入 js 一样。

于 2019-04-17T09:42:00.930 回答
1

您可以尝试COOP,这是一个程序员友好的 C 语言 OOP 框架,具有类、异常、多态性和内存管理(对于嵌入式代码很重要)。这是一种相对轻量级的语法,请参阅那里的 Wiki 中的教程

于 2020-02-02T12:36:53.060 回答
0

对我来说,C 中的面向对象应该具有以下特性:

  1. 封装和数据隐藏(可以使用结构/不透明指针来实现)

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

  3. 构造函数和析构函数(不容易实现)

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

  5. 引用计数(或实现RAII的东西)

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

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

于 2009-01-06T05:47:58.880 回答
0

@Adam Rosenfield 很好地解释了如何使用 C 实现 OOP

此外,我建议您阅读

1)pjsip

一个非常好的用于 VoIP 的 C 库。您可以通过结构和函数指针表了解它如何实现 OOP

2) iOS 运行时

了解 iOS Runtime 如何为 Objective C 提供支持。它通过 isa 指针、元类实现 OOP

于 2014-03-31T07:38:54.773 回答
0

查看http://ldeniau.web.cern.ch/ldeniau/html/oopc/oopc.html。如果没有别的,通读文档是一种启发性的体验。

于 2016-08-08T12:39:35.183 回答
0

如果你需要写一点代码试试这个: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\n", p->age);
}
于 2017-08-25T23:58:40.057 回答
0

开源 Dynace 项目正是这样做的。它位于https://github.com/blakemcbride/Dynace

于 2019-03-27T21:25:03.930 回答