Object-orientation in C

前端 未结 22 1483
孤城傲影
孤城傲影 2020-11-22 15:26

What would be a set of nifty preprocessor hacks (ANSI C89/ISO C90 compatible) which enable some kind of ugly (but usable) object-orientation in C?

I am familiar with

相关标签:
22条回答
  • 2020-11-22 16:09

    Slightly off-topic, but the original C++ compiler, Cfront, compiled C++ to C and then to assembler.

    Preserved here.

    0 讨论(0)
  • 2020-11-22 16:10

    You can try out COOP, a programmer-friendly framework for OOP in C, features Classes, Exceptions, Polymorphism, and Memory Management (important for Embedded code). It's a relatively lightweight syntax, see the tutorial in the Wiki there.

    0 讨论(0)
  • 2020-11-22 16:13
    #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
    

    Here is a show of what is OO programming with C.

    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).

    0 讨论(0)
  • 2020-11-22 16:14

    I used to do this kind of thing in C, before I knew what OOP was.

    Following is an example, which implements a data-buffer which grows on demand, given a minimum size, increment and maximum size. This particular implementation was "element" based, which is to say it was designed to allow a list-like collection of any C type, not just a variable length byte-buffer.

    The idea is that the object is instantiated using the xxx_crt() and deleted using xxx_dlt(). Each of the "member" methods takes a specifically typed pointer to operate on.

    I implemented a linked list, cyclic buffer, and a number of other things in this manner.

    I must confess, I have never given any thought on how to implement inheritance with this approach. I imagine that some blend of that offered by Kieveli might be a good path.

    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_affffdta(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_affffdta(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_affffdta(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 was simply a typedef of int - I used it to remind me that it's length was variable from platform to platform (for porting).

    0 讨论(0)
  • 2020-11-22 16:16

    ffmpeg (a toolkit for video processing) is written in straight C (and assembly language), but using an object-oriented style. It's full of structs with function pointers. There are a set of factory functions that initialize the structs with the appropriate "method" pointers.

    0 讨论(0)
  • 2020-11-22 16:16

    If you really thinks catefully, even standard C library use OOP - consider FILE * as an example: fopen() initializes an FILE * object, and you use it use member methods fscanf(), fprintf(), fread(), fwrite() and others, and eventually finalize it with fclose().

    You can also go with the pseudo-Objective-C way which is not difficult as well:

    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;
    }
    // ...
    

    To use:

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

    This is what may be resulted from some Objective-C code like this, if a pretty-old Objective-C-to-C translator is used:

    @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];
    }
    
    0 讨论(0)
提交回复
热议问题