C'de nesne yönelimi


157

C'de çirkin (ancak kullanılabilir) bir nesne yönelimi sağlayan bir dizi şık önişlemci kesmek (ANSI C89 / ISO C90 uyumlu) ne olurdu?

Birkaç farklı nesne yönelimli dile aşinayım, bu yüzden lütfen "C ++ öğrenin!" " ANSI C ile Nesneye Yönelik Programlama " (dikkat: PDF formatında ) ve diğer bazı ilginç çözümler okudum , ama çoğunlukla seninle ilgileniyorum :-)!


Ayrıca bkz . C'ye nesne yönelimli kod yazabilir misiniz?


1
D'yi öğrenmek ve gerçekten ihtiyacınız olan yerlerde c uyumlu abi'yi kullanmak için yanıt verebilir miyim? Digitalmars.com/d
Tim Matthews

2
@Dinah: "Ayrıca bakınız" için teşekkür ederiz. Bu yazı ilginçti.

1
İlginç soru, neden C'de OOP'nin bir işlemci öncesi
hackini isteyesiniz

3
@Calyth: OOP'un yararlı olduğunu ve "Sadece gerçekten bir C derleyicisi olan bazı gömülü sistemlerle çalışıyorum" (yukarıdan) olduğunu düşünüyorum. Ayrıca, şık önişlemci kesmek bakmak ilginç bulmuyor musunuz?

Yanıtlar:


31

C Nesne Sistemi (COS) umut verici geliyor (hala alfa versiyonunda). Basitlik ve esneklik için mevcut kavramları asgari düzeyde tutmaya çalışır: açık sınıflar, metasınıflar, mülkiyet metasınıfları, jenerikler, multimethods, delegasyon, sahiplik, istisnalar, sözleşmeler ve kapanışları içeren tek tip nesne yönelimli programlama. Bunu anlatan bir taslak kağıt (PDF) var.

C'de istisna diğer OO dillerinde bulunan TRY-CATCH-FINALLY'nin C89 uygulamasıdır. Bir testsuite ve bazı örnekler ile birlikte gelir.

Her ikisi de C'de OOP üzerinde çok çalışan Laurent Deniau tarafından .


@vonbrand COS, son taahhüt geçen yaz olan github'a taşındı. Olgunluk taahhüt eksikliğini açıklayabilir.
philant

185

C sözdizimini daha nesne yönelimli bir dil gibi yapmaya çalışmak için önişlemci (ab) kullanımına karşı öneriyorum. En temel düzeyde, yalnızca düz yapıları nesne olarak kullanır ve bunları işaretçilerle aktarırsınız:

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

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

Kalıtım ve polimorfizm gibi şeyleri elde etmek için biraz daha fazla çalışmalısınız. Bir yapının ilk üyesinin üst sınıfın bir örneği olmasını sağlayarak el ile miras alabilirsiniz ve daha sonra temel ve türetilmiş sınıfları serbestçe temel almak için işaretçiler çevresinde dolaşabilirsiniz:

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

Çok biçimlilik (yani sanal işlevler) elde etmek için, işlev işaretçileri ve isteğe bağlı olarak sanal tablolar veya vtables olarak da bilinen işlev işaretçi tablolarını kullanırsınız:

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

Ve C'de polimorfizmi böyle yaparsınız. Güzel değil, ama işi yapıyor. Temel ve türetilmiş sınıflar arasında, temel sınıf türetilmiş sınıfın ilk üyesi olduğu sürece güvenli olan işaretçi dökümleriyle ilgili bazı yapışkan sorunlar vardır. Birden fazla miras çok daha zordur - bu durumda, birinciden başka temel sınıflar arasında durum için, gerçekten zor ve hataya eğilimli uygun ofsetlere göre işaretçilerinizi manuel olarak ayarlamanız gerekir.

Yapabileceğiniz başka bir (zor) şey, çalışma zamanında bir nesnenin dinamik türünü değiştirmektir! Sadece yeni bir seçilebilir işaretçiyi yeniden atarsınız. Hatta bazı sanal işlevleri seçerek başkalarını tutarken yeni hibrit türler oluşturabilirsiniz. Global vtable'ı değiştirmek yerine yeni bir vtable oluşturmaya dikkat edin, aksi takdirde belirli bir türdeki tüm nesneleri yanlışlıkla etkileyeceksiniz.


6
Adam, bir tür küresel vtable değiştirme eğlenceli C :) ördek yazarak simüle etmektir.
jmucchiello

Şimdi C ++ üzücü ... Tabii ki C ++ sözdizimi daha net, ama önemsiz bir sözdizimi olmadığından hafifletildi. C ++ ve C arasında karma bir şey elde edilip edilemeyeceğini merak ediyorum, bu nedenle void * hala geçerli döküm tipi olabilir. İle bölüm struct derived {struct base super;};bayt tarafından doğru olduğundan sipariş yana, nasıl çalıştığını tahmin etmek açıktır.
jokoon

2
İyi yazılmış zarif kod için +1. Tam da aradığım şey buydu!
Homunculus Reticulli

3
Aferin. Ben de tam olarak bunu yapıyorum ve bu da doğru bir yol. Akılda bulunan yapıya / nesneye bir işaretçi yerine, bir tamsayıya (adres) bir işaretçi iletmeniz yeterlidir. Bu, sınırsız polimorfik yöntem çağrıları için her türlü nesneyi geçirmenize izin verecektir. Ayrıca, eksik olan tek şey yapılarınızı (nesneler / sınıflar) başlatmak için bir işlevdir. Bu bir malloc işlevi içerir ve bir işaretçi döndürür. Belki de C 'de mesaj geçişi (objektif-c) nasıl yapılacağına dair bir parça ekleyeceğim

1
Bu saman beni C ++ kırdı ve C daha fazla kullanmak için (sadece C ++ miras için kullanmadan önce) Teşekkür ederim
Anne Quinn

31

Bir zamanlar beni oldukça zarif bir şekilde uygulayan bir C kütüphanesiyle çalıştım. C'de nesneleri tanımlamanın bir yolunu yazmışlar, sonra onlardan miras alınmışlar ki C ++ nesnesi kadar genişletilebilirlerdi. Temel fikir şuydu:

  • Her nesnenin kendi dosyası vardı
  • Ortak işlevler ve değişkenler bir nesnenin .h dosyasında tanımlanır
  • Özel değişkenler ve işlevler yalnızca .c dosyasında bulundu
  • "Devralmak" için, yapının ilk üyesi devralınacak nesnedir.

Devralınmayı tarif etmek zordur, ancak temelde bu buydu:

struct vehicle {
   int power;
   int weight;
}

Sonra başka bir dosyada:

struct van {
   struct vehicle base;
   int cubic_size;
}

O zaman bellekte yaratılan ve sadece araçlar hakkında bilgi sahibi olan kodla kullanılan bir minibüsünüz olabilir:

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

Güzel çalıştı ve .h dosyaları her nesne ile ne yapabileceğinizi tam olarak tanımladı.


Bu özümü gerçekten çok seviyorum, ancak tüm "nesne" nin iç kısımları herkese açık.
Lawrence Dol

6
@Yazılım Maymunu: C'nin erişim kontrolü yoktur. Uygulama ayrıntılarını gizlemenin tek yolu, oldukça ağrılı olabilen opak işaretçilerle etkileşim kurmaktır, çünkü tüm alanlara muhtemelen satır içine girilemeyen erişimci yöntemleriyle erişilmesi gerekecektir.
Adam Rosenfield

1
@Adam: Bağlantı zamanı optimizasyonlarını destekleyen derleyiciler onları iyi sıralayacak ...
Christoph

9
Bunu yaparsanız, .c dosyasındaki genel olarak tanımlanmayan tüm işlevlerin statik olarak tanımlandığından emin olun, böylece nesne dosyalarınızda adlandırılmış işlevler olarak görünmezler. Bu, hiç kimsenin bağlantı aşamasında isimlerini bulamamasını sağlar.
jmucchiello

2
@Marcel: C, kod otonom sistemler için çeşitli işlemciler çalıştıran düşük seviyeli kartlara yerleştirildiği için kullanıldı. Hepsi C'den kendi yerel ikili dosyalarına derlemeyi destekledi. Bu yaklaşım, ne yapmaya çalıştıklarını anladıktan sonra kodun okunmasını çok kolay hale getirdi.
Kieveli

18

Linux için GNOME masaüstü nesne yönelimli C ile yazılmıştır ve özellikleri, miras, polimorfizm ve referanslar, olay işleme ("sinyaller" olarak adlandırılır), çalışma zamanı gibi diğer bazı özellikleri destekleyen " GObject " adlı bir nesne modeline sahiptir. yazma, özel veri vb.

Sınıf hiyerarşisinde daktiloda yazmak gibi şeyler yapmak için önişlemci korsanlıklarını içerir. İşte GNOME için yazdığım örnek bir sınıf (gchar gibi şeyler typedefs):

Sınıf Kaynağı

Sınıf Başlığı

GObject yapısının içinde GLib'in dinamik yazım sistemi için sihirli bir sayı olarak kullanılan bir GType tamsayısı vardır (türünü bulmak için tüm yapıyı bir "GType" a atabilirsiniz).


ne yazık ki, beni oku / öğretici dosya (wiki bağlantısı) çalışmıyor ve bunun için sadece referans kılavuzu (GTK değil GObject hakkında konuşuyorum). lütfen aynı için bazı öğretici dosyalar sağlayın ...
FL4SOF

Bağlantılar düzeltildi.
James Cape

4
Bağlantılar tekrar koptu.
SeanRamey

6

OOP'un ne olduğunu bilmeden önce C'de bu tür şeyleri yapardım.

Aşağıda, minimum boyut, artış ve maksimum boyut göz önüne alındığında, talep üzerine büyüyen bir veri tamponu uygulayan bir örnek verilmiştir. Bu özel uygulama "eleman" tabanlıdır, yani, sadece değişken uzunluklu bir bayt-tamponu değil, herhangi bir C tipinin liste benzeri bir koleksiyonuna izin vermek üzere tasarlanmıştır.

Buradaki fikir, nesnenin xxx_crt () kullanılarak başlatılması ve xxx_dlt () kullanılarak silinmesidir. "Üye" yöntemlerinin her biri üzerinde çalışmak için özel olarak yazılmış bir işaretçi alır.

Bağlantılı bir liste, döngüsel tampon ve diğer bazı şeyleri bu şekilde uyguladım.

İtiraf etmeliyim, bu yaklaşımla kalıtımın nasıl uygulanacağı hakkında hiç bir fikir vermedim. Kieveli tarafından sunulan karışımın bazı karışımlarının iyi bir yol olabileceğini hayal ediyorum.

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 sadece int bir typedef'ti - uzunluğunun platformdan platforma (taşıma için) değişken olduğunu hatırlatmak için kullandım.


7
kutsal moly, bu gizli bir C yarışması kazanabilir! bunu sevdim! :)
horseyguy

@horseyguy Hayır olamazdı. Yayınlandı. Ayrıca iocccsize aracına karşı başlık dosyalarının kötüye kullanılmasını da içerirler. Ayrıca tam bir program değildir. 2009 yarışması olmadı, bu yüzden iocccsize'i karşılaştıramazsınız. CPP birçok kez istismar edildi, bu yüzden oldukça eski. Vb Üzgünüm. Olumsuz olmaya çalışmıyorum ancak gerçekçi oluyorum. Anlamını bir şekilde anlıyorum ve bu iyi bir okuma ve yukarı oy verdim. (Ve evet buna katılıyorum ve evet ben de kazanıyorum.)
Pryftan

6

Biraz konu dışı, ancak orijinal C ++ derleyici Cfront , C ++ C ve sonra birleştirici derledi.

Burada korunmuş .


Aslında daha önce görmüştüm. Güzel bir iş olduğuna inanıyorum.

Anthony Cuozzo: Stan Lippman, 'C ++ - Nesne modelinin içinde' adında harika bir kitap yazdı ve burada c-cephesini yazma ve sürdürme konusundaki deneyimlerinin ve tasarım kararlarının çoğunu ilişkilendirdi. Hala iyi bir okuma ve yıllarca C'den C ++ 'a geçiş yaparken bana çok yardımcı oldu
zebrabox

5

Nesnelere çağrılan yöntemleri this, işlevin içine örtük bir ' ' geçiren statik yöntemler olarak düşünüyorsanız, C'de OO düşünmeyi kolaylaştırabilir.

Örneğin:

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

dönüşür:

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

Ya da böyle bir şey.


6
@Artelius: Tabii, ama bazen belli olana kadar, söylenene kadar. Bunun için +1.
Lawrence Dol

1
daha iyi olurstring->length(s);
OozeMeister

4

ffmpeg (video işleme için bir araç takımı) düz C (ve montaj dili) ile yazılır, ancak nesne yönelimli bir stil kullanılır. İşlev işaretçileri olan yapılarla doludur. Yapıları uygun "yöntem" işaretçileriyle başlatan bir dizi fabrika işlevi vardır.


Ben herhangi bir fabrika fonksiyonları (ffmpeg) görmüyorum, daha ziyade polimorfizm / miras (yukarıda önerilen önemsiz yol) kullanarak görünmüyor.
FL4SOF

avcodec_open bir fabrika fonksiyonudur. Fonksiyon işaretçileri bir AVCodecContext yapısına (draw_horiz_band gibi) doldurur. Avcodec.h dosyasında FF_COMMON_FRAME makro kullanımına bakarsanız, veri üyelerinin miras alınmasına benzer bir şey görürsünüz. IMHO, ffmpeg bana OOP'un en iyi C ++ 'da yapıldığını kanıtlıyor, C değil
Bay Fooz

3

- Eğer gerçekten catefully düşünüyorsa, hatta standart C kütüphanesi kullanımı cepten düşünün FILE *bir örnek olarak: fopen()bir başlatır FILE *nesneyi ve bunu üye yöntemleri kullanmak kullanmak fscanf(), fprintf(), fread(), fwrite()ve diğerleri, ve sonunda onu sonuçlandırmak fclose().

Ayrıca zor olmayan sözde-Objective-C yolu ile de gidebilirsiniz:

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

Kullanmak:

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

Oldukça eski bir Objective-C-to-C tercümanı kullanılırsa, böyle bir Objective-C kodundan kaynaklanabilecek olan şey budur:

@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];
}

Ne mu __attribute__((constructor))yapmak void __meta_Foo_init(void) __attribute__((constructor))?
AE Drew

1
İkili belleğe yüklendiğinde işaretli işlevin çağrılmasını sağlayan bir GCC uzantısıdır. @AEDrew
Maxthon Chan

popen(3)FILE *başka bir örnek için de a döndürür .
Pryftan

3

Adam Rosenfield'ın yayınladığı şey C'de OOP yapmanın doğru yolu. Gösterdiği şeyin nesnenin uygulanması olduğunu eklemek isterim. Başka bir deyişle, gerçek uygulama .cdosyaya konulurken, arayüz üstbilgiye konur.h dosyasına . Örneğin, yukarıdaki maymun örneğini kullanarak:

Arayüz şöyle görünecektir:

//monkey.h

    struct _monkey;

    typedef struct _monkey monkey;

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

Arayüz .hdosyasında sadece prototipleri tanımladığınızı görebilirsiniz. Daha sonra, uygulama parçası " .cdosyasını" statik veya dinamik bir kitaplıkta derleyebilirsiniz . Bu kapsülleme oluşturur ve ayrıca uygulamayı istediğiniz zaman değiştirebilirsiniz. Nesnenizin kullanıcısı, nesnenin uygulanması hakkında neredeyse hiçbir şey bilmemelidir. Bu aynı zamanda nesnenin genel tasarımına da odaklanır.

Kişisel inancım, oop'un kod yapınızı ve yeniden kullanılabilirliği kavramsallaştırmanın bir yolu olduğu ve c ++ gibi aşırı yükleme veya şablonlara eklenen diğer şeylerle gerçekten ilgisi olmadığını. Evet, bunlar çok güzel yararlı özelliklerdir, ancak nesne yönelimli programlamanın gerçekte ne olduğunu temsil etmezler.


Sen bir yapı bildirmek olabilir typedef struct Monkey {} Monkey; oluşturulduktan geçirildikten sonra onu typedef'ing noktası nedir?
MarcusJ

1
@MarcusJ struct _monkeyBasitçe bir prototip. Gerçek tür tanımı, uygulama dosyasında (.c dosyası) tanımlanır. Bu, kapsülleme etkisini oluşturur ve API geliştiricisinin, API'yı değiştirmeden gelecekte maymun yapısını yeniden tanımlamasına olanak tanır. API kullanıcılarının yalnızca gerçek yöntemlerle ilgilenmeleri gerekir. API tasarımcısı, nesnenin / yapının nasıl düzenlendiğini de içeren uygulama ile ilgilenir. Böylece nesne / yapı ayrıntıları kullanıcıdan gizlenir (opak bir tür).

Yapılarımı başlıklarda tanımlıyorum, bu standart değil mi? Bunu bu şekilde yapıyorum çünkü bazen o kütüphanenin dışındaki yapı üyelerine erişmem gerekiyor.
MarcusJ

1
@MarcusJ İsterseniz üstbilgilerde yapı tanımlayabilirsiniz (standart yoktur). Ancak iç yapıyı yoldan değiştirmek istiyorsanız kodunuzu kırabilirsiniz. Kapsülleme, yalnızca kodunuzu kırmadan bir uygulamayı değiştirmeyi kolaylaştıran bir kodlama stilidir. int getCount(ObjectType obj)Uygulama dosyasında yapıyı tanımlamayı seçerseniz , üyelerinize vb gibi erişimci yöntemleriyle her zaman erişebilirsiniz .

2

Benim tavsiyem: basit tut. Sahip olduğum en büyük sorunlardan biri eski yazılımları (bazen 10 yaşın üzerinde) korumaktır. Kod basit değilse, zor olabilir. Evet, C'de polimorfizm ile çok yararlı bir OOP yazılabilir, ancak okunması zor olabilir.

Bazı iyi tanımlanmış işlevleri kapsülleyen basit nesneleri tercih ederim. Buna harika bir örnek GLIB2'dir , örneğin bir karma tablosu:

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

Anahtarlar:

  1. Basit mimari ve tasarım deseni
  2. Temel OOP kapsülleme elde eder.
  3. Uygulaması, okunması, anlaşılması ve bakımı kolaydır

1

Eğer CI'de OOP yazacak olsaydım muhtemelen sahte bir Pimpl ile giderdim. tasarımıyla . İşaretçileri yapılara geçirmek yerine, işaretçileri yapılara göstermek için sonuç verirsiniz. Bu, içeriği opak hale getirir ve polimorfizmi ve kalıtımı kolaylaştırır.

C'deki OOP ile ilgili asıl sorun, değişkenler kapsamdan çıktığında olan şeydir. Derleyici tarafından oluşturulan yok edici yoktur ve bu da sorunlara neden olabilir. Makrolar muhtemelen yardımcı olabilir, ancak bakmak her zaman çirkin olacaktır.


1
C'de programlama yaparken, ififadeleri kullanarak ve sonunda onları serbest bırakarak kapsamla ilgilenirim . Örneğinif ( (obj = new_myObject()) ) { /* code using myObject */ free_myObject(obj); }

1

C ile nesne yönelimli bir tarzda programlamanın başka bir yolu da, alana özel bir dili C'ye dönüştüren bir kod üreteci kullanmaktır. OOP'yi js'ye getirmek için TypeScript ve JavaScript ile yapıldığı gibi.


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

Çıktı:

6.56
13.12

İşte C ile OO programlamanın ne olduğunu gösteren bir program.

Bu gerçek, saf C, önişlemci makroları yok. Kalıtım, polimorfizm ve veri kapsülleme (sınıflara veya nesnelere özel veriler dahil) var. Korunan niteleyici eşdeğeri için bir şans yoktur, yani özel veriler de miras zincirinde özeldir. Ama bu bir rahatsızlık değil çünkü gerekli olduğunu düşünmüyorum.

CPolygon somutlaştırılmamıştır, çünkü bunu yalnızca ortak yönleri olan ancak farklı uygulama biçimlerine (Polimorfizm) sahip olan miras zincirindeki nesneleri manipüle etmek için kullanıyoruz.


0

Adam Rosenfield'ın C ile OOP'ye nasıl ulaşılacağı hakkında çok iyi bir açıklaması var

Ayrıca, okumanızı tavsiye ederim

1) pjsip

VoIP için çok iyi bir C kütüphanesi. Yapılar ve işlev işaretçi tabloları üzerinden nasıl OOP elde ettiğini öğrenebilirsiniz

2) iOS Çalışma Zamanı

İOS Çalışma Zamanı'nın Hedef C'yi nasıl güçlendirdiğini öğrenin.


0

Benim için C'deki nesne yönelimi şu özelliklere sahip olmalıdır:

  1. Kapsülleme ve veri gizleme (yapılar / opak işaretçiler kullanılarak gerçekleştirilebilir)

  2. Polimorfizm için kalıtım ve destek (tek kalıtım, yapılar kullanılarak elde edilebilir - soyut tabanın somut olmadığından emin olun)

  3. Yapıcı ve yıkıcı işlevselliği (elde edilmesi kolay değil)

  4. Tip kontrolü (en azından C hiçbirini uygulamadığı için kullanıcı tanımlı tipler için)

  5. Referans sayımı (veya RAII'yi uygulamak için bir şey )

  6. İstisna yönetimi için sınırlı destek (setjmp ve longjmp)

Yukarıdakilerin üstünde ANSI / ISO spesifikasyonlarına ve derleyiciye özgü işlevselliğe dayanmamalıdır.


Sayı için (5) - RAII'yi yıkıcılar olmadan bir dilde uygulayamazsınız (yani RAII, C veya Java'da derleyici destekli bir teknik değildir).
Tom

yapıcılar ve yıkıcılar c tabanlı nesne için yazılabilir - sanırım GObject yapar. ve tabii RAAI (düz ileri değil, çirkin olabilir ve hiç pragmatik olmasına gerek yoktur) - tüm aradığım yukarıda belirtilenleri başarmak için C tabanlı anlambilimi tanımlamaktır.
FL4SOF

C yıkıcıları desteklemez. Bunların çalışması için bir şeyler yazmanız gerekir . Bu, kendilerini temizlemedikleri anlamına gelir. GObject dili değiştirmez.
Tom

0

Http://ldeniau.web.cern.ch/ldeniau/html/oopc/oopc.html adresine bakın . Belgeleri okumaktan başka hiçbir şey aydınlatıcı bir deneyim değilse.


3
Lütfen paylaştığınız bağlantı için bağlam sağlayın. Paylaştığınız bağlantı gerçekten çok yararlı olsa da, soruyu yanıtlayan paylaşılan makalenin önemli yönlerini yakalamanız önerilir. Bu şekilde, bağlantı kaldırılsa bile yanıtınız yine de alakalı ve yardımcı olacaktır.
ishmaelMakitla

0

Burada partiye biraz geç kaldım ama her iki makro aşırılıktan kaçınmayı seviyorum - çok fazla veya çok fazla kod gizliyor, ancak birkaç belirgin makro OOP kodunu geliştirmeyi ve okumayı daha kolay hale getirebilir:

/*
 * 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
}

Bunun iyi bir dengeye sahip olduğunu ve daha olası hataların bazıları için oluşturduğu hataların (en azından varsayılan gcc 6.3 seçenekleriyle) kafa karıştırmak yerine yararlı olduğunu düşünüyorum. Bütün mesele programcı verimliliğini artırmak değil mi?



0

Ben de bir makro çözüm dayalı bu üzerinde çalışıyorum. Bu yüzden sadece en cesur olanlar için, sanırım ;-) Ama zaten çok güzel ve zaten bunun üzerine birkaç proje üzerinde çalışıyorum. Her sınıf için ayrı bir başlık dosyası tanımlamanız için çalışır. Bunun gibi:

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

Sınıfı uygulamak için, bunun için bir başlık dosyası ve yöntemleri uyguladığınız bir C dosyası oluşturursunuz:

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

Sınıf için oluşturduğunuz başlığa, ihtiyacınız olan diğer başlıkları eklersiniz ve sınıfla ilgili türleri vb. Tanımlayabilirsiniz. Hem sınıf üstbilgisine hem de C dosyasına sınıf belirtim dosyasını (ilk kod örneğine bakın) ve bir X makrosunu dahil edersiniz. Bu X makroları ( 1 , 2 , 3 vb.) Kodu gerçek sınıf yapılarına ve diğer bildirimlere genişletir.

Bir sınıfı devralmak #define SUPER supernameve eklemek içinsupername__define \ ve sınıf tanımında ilk satır olarak için. İkisi de orada olmalı. Ayrıca JSON desteği, sinyaller, soyut sınıflar vb.

Bir nesne oluşturmak için kullanın W_NEW(classname, .x=1, .y=2,...). Başlatma, C11'de sunulan yapı başlatmaya dayanır. Güzel çalışıyor ve listelenmeyen her şey sıfıra ayarlanmış.

Bir yöntemi çağırmak için tuşunu kullanın W_CALL(o,method)(1,2,3). Daha üst düzey bir işlev çağrısı gibi görünüyor, ancak sadece bir makro. ((o)->klass->method(o,1,2,3))Gerçekten güzel bir hack olan genişler .

Bkz. Belgeler ve kodun kendisi .

Çerçeve bazı kaynak koduna ihtiyaç duyduğundan, işi yapan bir Perl betiği (wobject) yazdım. Bunu kullanırsanız, sadece yazabilirsiniz

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

ve sınıf belirtim dosyasını, sınıf başlığını ve Point_impl.csınıfı uyguladığınız yeri içeren bir C dosyasını oluşturur . Birçok basit sınıfınız varsa, ancak yine de her şey C'de ise oldukça fazla iş tasarrufu sağlar. Wobject , belirli ihtiyaçlara uyarlanması veya sıfırdan yeniden yazılması kolay olan çok basit bir düzenli ifade tabanlı tarayıcıdır.



0

Sen deneyebilirsiniz KÜMES , C OOP bir programcı dostu çerçeveyi Sınıfları, İstisnalar özellikleri, Polimorfizm ve Bellek Yönetimi (Gömülü kodu için önemli). Nispeten hafif bir sözdizimi, Wiki'deki eğiticiye bakın .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.