当前位置:  开发笔记 > 编程语言 > 正文

C中的面向对象

如何解决《C中的面向对象》经验,为你挑选了7个好方法。

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

我熟悉一些不同的面向对象语言,所以请不要回答"学习C++!"这样的答案.我读过" 面向对象的ANSI C编程 "(当心:PDF格式)和其他一些有趣的解决方案,但我最感兴趣的是你:-)!


另请参见您能用C编写面向对象的代码吗?



1> Adam Rosenfi..:

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

要获得多态(即虚函数),可以使用函数指针,也可以使用函数指针表,也称为虚拟表或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,否则你会意外地影响给定类型的所有对象.


亚当,改变一种类型的全局vtable的乐趣是模拟C中的鸭子类型:)
做得好.这正是我一直在做的事情,这也是正确的方法.您不应该只需要指向结构/对象的指针,而应该只是将指针传递给整数(地址).这将允许您传入任何类型的对象以进行无限制的多态方法调用.此外,唯一缺少的是初始化结构(对象/类)的函数.这将包括malloc函数并返回指针.也许我会在C中添加一条如何进行消息传递(objective-c)的内容.
优雅的代码+1,写得很好.这正是我想要的!

2> Kieveli..:

我曾经使用过一个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文件确切地定义了你应该能够对每个对象做什么.


如果执行此操作,还应确保.c文件中未定义为public的所有函数都定义为static,因此它们不会最终作为目标文件中的命名函数.这确保没有人能够在链接阶段找到他们的名字.
@Software Monkey:C没有访问控制.隐藏实现细节的唯一方法是通过不透明指针进行交互,这会非常痛苦,因为所有字段都需要通过可能无法内联的访问器方法来访问.
我想更多地了解这是如何工作的:)
@Marcel:使用C是因为代码部署在运行各种自治系统处理器的低级板上.他们都支持从C编译到他们各自的原生二进制文件.一旦你意识到他们想要做什么,这种方法使代码非常容易阅读.

3> philant..:

C对象系统(COS)听起来很有前途(它仍然是alpha版本).为了简单和灵活,它试图保持最小的可用概念:统一的面向对象编程,包括开放类,元类,属性元类,泛型,多方法,委托,所有权,异常,契约和闭包.有一份描述它的草案文件(PDF).

C中的例外是在其他OO语言中找到的TRY-CATCH-FINALLY的C89实现.它附带了一个测试套件和一些例子.

两个人都是Laurent Deniau,他在C中的OOP上工作很多.



4> James Cape..:

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

它包括预处理器hacks,用于在类层次结构中进行类型转换等操作.这是我为GNOME编写的一个示例类(像gchar这样的东西是typedef):

班级来源

班级标题

在GObject结构中,有一个GType整数,用作GLib动态类型系统的幻数(你可以将整个结构转换为"GType"来找到它的类型).


指向示例代码的链接无效.
链接再次断开。

5> jjnguy..:

如果你认为将对象调用的方法作为将隐式' this' 传递给函数的静态方法,它可以使C中的OO更容易思考.

例如:

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

变为:

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

或类似的东西.


@Artelius:当然,但有时显然不是,直到它陈述.+1为此.

6> zebrabox..:

稍微偏离主题,但最初的C++编译器,Cfront,将C++编译为C,然后编译为汇编程序.

保存在这里.



7> Lawrence Dol..:

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

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

我们的想法是使用xxx_crt()实例化对象,并使用xxx_dlt()删除.每个"成员"方法都采用特定类型的指针进行操作.

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

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

dtb.c:

#include 
#include 
#include 

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 || maxsizmaxsiz)     { 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 - 我用它来提醒我,它的长度在平台之间是可变的(用于移植).


圣洁的莫莉,这可能会赢得一场模糊的C比赛!我喜欢!:)
推荐阅读
刘美娥94662
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有