Bir kimse nasıl nesne yönelimli kod C yazmak? [kapalı]


500

C nesneye yönelik kod yazmanın bazı yolları nelerdir? Özellikle polimorfizm açısından.


Ayrıca bkz. Bu Yığın Taşması sorusu C'deki Nesne yönelimi .


1
Laurent Deniau tarafından


25
@Camilo Martin: Ben kasten sorulan can not gerektiği . Aslında C'de OOP kullanmakla ilgilenmiyorum. Bununla birlikte, C'de OO çözümleri görerek, C'nin sınırları ve / veya esnekliği hakkında ve ayrıca polimorfizmi uygulamanın ve kullanmanın yaratıcı yolları hakkında daha fazla bilgi edinmeye devam ediyoruz.
Dinah

5
OO sadece bir model. Burada kontrol edin, hatta .bat dosyalarında yapılabilir : dirk.rave.org/chap9.txt (yeterince ilgileniyorsanız, herhangi bir desen herhangi bir programlama diline uygulanabilir, sanırım). Bu düşünce için iyi bir besindir. Ve muhtemelen onlara sahip olmayan dillere verilen bu kalıpları uygulayarak çok şey öğrenilebilir.
Camilo Martin

6
GTK - 'beni korkut, GObject - aslında C'deki OOP (sorta)' nın oldukça iyi bir örneğidir.
new123456

Yanıtlar:


362

Evet. Aslında Axel Schreiner, “ANSI-C'de Nesneye Yönelik Programlama” kitabını ücretsiz olarak sunuyor ve konuyu oldukça kapsamlı bir şekilde ele alıyor.


28
Bu kitaptaki kavramlar katı olsa da, tür güvenliğini kaybedeceksiniz.
diyapir

22
Tasarım örüntüleri olarak bildiğimiz şeyden önce, "nesne yönelimi" olarak bilinen tasarım örüntüsü; çöp toplama ile aynı ve diğer. Şimdi çok kökleşmişler, unutmaya meyilliyiz, ilk tasarlandıklarında, bugün tasarım desenleri olarak düşündüğümüzle aynı şekilde oldu
Dexygen

11
Doğrudan yazarın sitesinden alabilirsiniz: cs.rit.edu/~ats/books/ooc.pdf aynı yazarın diğer makaleleri: cs.rit.edu/~ats/books/index.html
pakman

10
Uygun koleksiyon (Kitap + kaynak kodu örnekleri) bu rit.edu dizininden alınabilir ANSI-C ile nesne yönelimli programlama
David C. Rankin

3
Bu kitap hakemli mi? İlk sayfanın ilk paragrafının ilk cümlesinde bir yazım hatası vardır.
Dagrooms

343

Polimorfizmden bahsettiğiniz için evet, yapabilirsiniz, C ++ ortaya çıkmadan yıllar önce bu tür şeyler yapıyorduk.

Temel olarak struct, verileri tutmak için a öğesini ve bu verilerle ilgili işlevleri işaret etmek için işlev işaretçileri listesini kullanırsınız.

Bu nedenle, bir iletişim sınıfında, bir nesnenin verilerinin yanı sıra, bir nesnenin verilerinin yanında yapıda dört işlev işaretçisi olarak tutulacak bir açık, okuma, yazma ve kapatma çağrınız olur:

typedef struct {
    int (*open)(void *self, char *fspec);
    int (*close)(void *self);
    int (*read)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    int (*write)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    // And data goes here.
} tCommClass;

tCommClass commRs232;
commRs232.open = &rs232Open;
: :
commRs232.write = &rs232Write;

tCommClass commTcp;
commTcp.open = &tcpOpen;
: :
commTcp.write = &tcpWrite;

Tabii ki, yukarıdaki kod bölümleri aslında gibi bir "yapıcı" olacaktır rs232Init().

Bu sınıftan 'miras aldığınızda' sadece işaretçileri kendi işlevlerinizi gösterecek şekilde değiştirirsiniz. Bu işlevleri çağıran herkes bunu işlev işaretçileri aracılığıyla yaparak polimorfizminizi verir:

int stat = (commTcp.open)(commTcp, "bigiron.box.com:5000");

Manuel bir vtable gibi.

İşaretçileri NULL olarak ayarlayarak sanal sınıflara bile sahip olabilirsiniz. Davranış C ++ 'dan biraz farklı olacaktır (derleme zamanında bir hata yerine çalışma zamanında çekirdek dökümü).

İşte bunu gösteren bir örnek kod parçası. İlk olarak üst düzey sınıf yapısı:

#include <stdio.h>

// The top-level class.

typedef struct sCommClass {
    int (*open)(struct sCommClass *self, char *fspec);
} tCommClass;

Sonra TCP 'alt sınıfı' için fonksiyonlarımız var:

// Function for the TCP 'class'.

static int tcpOpen (tCommClass *tcp, char *fspec) {
    printf ("Opening TCP: %s\n", fspec);
    return 0;
}
static int tcpInit (tCommClass *tcp) {
    tcp->open = &tcpOpen;
    return 0;
}

Ve HTTP olanı da:

// Function for the HTTP 'class'.

static int httpOpen (tCommClass *http, char *fspec) {
    printf ("Opening HTTP: %s\n", fspec);
    return 0;
}
static int httpInit (tCommClass *http) {
    http->open = &httpOpen;
    return 0;
}

Ve son olarak bunu eylem halinde göstermek için bir test programı:

// Test program.

int main (void) {
    int status;
    tCommClass commTcp, commHttp;

    // Same 'base' class but initialised to different sub-classes.

    tcpInit (&commTcp);
    httpInit (&commHttp);

    // Called in exactly the same manner.

    status = (commTcp.open)(&commTcp, "bigiron.box.com:5000");
    status = (commHttp.open)(&commHttp, "http://www.microsoft.com");

    return 0;
}

Bu çıktıyı üretir:

Opening TCP: bigiron.box.com:5000
Opening HTTP: http://www.microsoft.com

böylece alt sınıfa bağlı olarak farklı işlevlerin çağrıldığını görebilirsiniz.


52
Kapsülleme oldukça kolaydır, polimorfizm yapılabilir - ancak kalıtım zor
Martin Beckett

5
Geçenlerde bir makale başlıklı yayınlanmış lwn.net çekirdekte Object Oriented tasarım Desenleri , fonksiyon işaretçileri içeren bir yapı veya bir gösterici alan işlevleri olan bir yapı için bir gösterici - Yukarıdaki cevaba benzer stucts konusunda parametre olarak üzerinde çalıştığımız verilerle yapılandırın.
radicalmatt

11
+1 Güzel örnek! Herkes bu yoldan gerçekten gitmek istese de, "örnek" yapılarının "sanal tablo" örneğine işaret eden tek bir alana sahip olması ve bu tür için tüm sanal işlevleri tek bir yerde içermesi daha uygun olacaktır . Yani tCommClassyeniden adlandırılacak tCommVTve bir tCommClassyapı sadece veri alanlarına ve tCommVT vt"bir ve tek" sanal tabloya işaret eden tek bir alana sahip olacaktı . Her örnekle tüm işaretçileri taşımak gereksiz ek yükler ekler ve JavaScript'te C ++, IMHO'dan daha fazla şey nasıl yapacağınıza benzer.
Groo

1
Yani bu, tek bir arabirimin uygulanmasını gösterir, ancak birden fazla arabirimin uygulanmasına ne dersiniz? Veya birden fazla miras?
weberc2

Weber, C ++ 'nın tüm fonksiyonelliğini istiyorsanız, muhtemelen C ++ kullanıyor olmalısınız. Soru özellikle polimorfizm, nesnelerin farklı bir "form" alma yeteneği hakkında sordu. C'de kesinlikle arayüzler ve çoklu kalıtsallık yapabilirsiniz, ancak bu oldukça fazladan bir iştir ve akıllıca C ++ yerleşik şeyleri kullanmak yerine kendiniz yönetmeniz gerekir.
paxdiablo

86

Ad alanları genellikle şu şekilde yapılır:

stack_push(thing *)

onun yerine

stack::push(thing *)

Bir yapmak için C bir gibi bir şey haline yapı C ++ Eğer açabilirsiniz sınıfa:

class stack {
     public:
        stack();
        void push(thing *);
        thing * pop();
        static int this_is_here_as_an_example_only;
     private:
        ...
};

içine

struct stack {
     struct stack_type * my_type;
     // Put the stuff that you put after private: here
};
struct stack_type {
     void (* construct)(struct stack * this); // This takes uninitialized memory
     struct stack * (* operator_new)(); // This allocates a new struct, passes it to construct, and then returns it
     void (*push)(struct stack * this, thing * t); // Pushing t onto this stack
     thing * (*pop)(struct stack * this); // Pops the top thing off the stack and returns it
     int this_is_here_as_an_example_only;
}Stack = {
    .construct = stack_construct,
    .operator_new = stack_operator_new,
    .push = stack_push,
    .pop = stack_pop
};
// All of these functions are assumed to be defined somewhere else

Ve yapın:

struct stack * st = Stack.operator_new(); // Make a new stack
if (!st) {
   // Do something about it
} else {
   // You can use the stack
   stack_push(st, thing0); // This is a non-virtual call
   Stack.push(st, thing1); // This is like casting *st to a Stack (which it already is) and doing the push
   st->my_type.push(st, thing2); // This is a virtual call
}

Yıkıcı yapmadım ya da sildim, ama aynı kalıbı takip ediyor.

this_is_here_as_an_example_only, bir türün tüm örnekleri arasında paylaşılan statik bir sınıf değişkeni gibidir. Tüm yöntemler gerçekten statiktir, ancak bazıları bunu alır *


1
@nategoose - st->my_type->push(st, thing2);yerinest->my_type.push(st, thing2);
Fabricio

@nategoose: VEYA struct stack_type my_type; yerinestruct stack_type * my_type;
Fabricio

3
Sınıf için bir yapıya sahip olma kavramını seviyorum. Peki genel bir Classyapıya ne dersiniz ? Bu, OO C'yi C ++ 'dan daha dinamik hale getirecektir . Peki ya bu? Bu arada +1.
Linuxios

54

Kendi başına faydalı olmanın yanı sıra, C'de OOP uygulamanın, OOP öğrenmek ve iç işleyişini anlamak için mükemmel bir yol olduğuna inanıyorum . Birçok programcının deneyimi, bir tekniği verimli ve güvenli bir şekilde kullanmak için, bir programcının temel kavramların nihayetinde nasıl uygulandığını anlaması gerektiğini göstermiştir. C'deki taklit sınıfları, kalıtım ve polimorfizm sadece bunu öğretir.

Orijinal soruyu cevaplamak için, C'de OOP'nin nasıl yapılacağını öğreten birkaç kaynak:

EmbeddedGurus.com blog yazısı "C'de nesne tabanlı programlama" taşınabilir C'de sınıfların ve tek mirasın nasıl uygulanacağını gösterir: http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c /

Uygulama Notu "" C + "- C'de Nesneye Yönelik Programlama" önişlemci makroları kullanarak C'de sınıfların, tek mirasın ve geç bağlamanın (polimorfizm) nasıl uygulanacağını gösterir: http://www.state-machine.com/resources/cplus_3. 0_manual.pdf , örnek kodu http://www.state-machine.com/resources/cplus_3.0.zip adresinde bulabilirsiniz.


4
C + kılavuzu için yeni url: state-machine.com/doc/cplus_3.0_manual.pdf
Liang

32

Yaptığını gördüm. Ben tavsiye etmem. C ++ başlangıçta bu yolu bir ara adım olarak C kodu üreten bir önişlemci olarak başlattı.

Esasen, sonuçta işlev referanslarınızı sakladığınız tüm yöntemleriniz için bir gönderim tablosu oluşturmaktır. Bir sınıfın türetilmesi, bu dağıtım tablosunun kopyalanmasını ve geçersiz kılmak istediğiniz girişlerin, temel yöntemi çağırmak istiyorsa orijinal yöntemi çağırmak zorunda olan yeni "yöntemleriniz" ile değiştirilmesini gerektirir. Sonunda, C ++ yeniden yazmak sonunda.


5
"Sonunda, C ++ yeniden yazmak sonunda" Ben böyle olacağını / korktu merak ettim.
Dinah

39
Ya da çok daha cazip bir sonuç olacak Hedef C'yi yeniden yazabilirsiniz.
Prof. Falken sözleşmesi

3
Javascript gibi OOP'un sınıfsız bir lezzeti var , burada guru şöyle diyor: "Çok sayıda benzer nesne yapmak için sınıflara ihtiyacımız yok." Ama bunun C'de başarılması kolay olmadığından korkuyorum. (Bir yapıyı klonlamak için bir klon () rutini var mı?)
Lumi

1
Bunu gerçekten uygulamak ve uygulamayı hızlı yapmak zorunda olan başka bir akıllı adam (Google, V8 motoru), JavaScript'e geri (gizli) sınıflar eklemek için her şeyi yaptı.
cubuspl42

Is not glibobjektif bir şekilde C ile yazılmış?
kravemir

26

Elbette bu mümkün. Bu nedir GObject , tüm bu çerçeve GTK + ve GNOME dayanmaktadır yapar.


Böyle bir yaklaşımın artıları / eksileri nelerdir? Yani. C ++ kullanarak yazmak çok daha kolay.
kravemir

@kravemir C ++, C kadar taşınabilir değildir ve C ++ 'yı farklı bir C ++ derleyicisi tarafından derlenebilecek koda bağlamak biraz daha zordur. Ancak evet, C ++ 'da sınıf yazmak daha kolaydır, ancak GObject de o kadar da zor değildir (küçük bir kazan plakası sakıncası yoksa).
Edwin Buck

17

C stdio FILE alt kütüphanesi, katkısız C'de soyutlama, kapsülleme ve modülerliğin nasıl oluşturulacağına mükemmel bir örnektir.

Kalıtım ve polimorfizm - genellikle OOP için gerekli görülen diğer yönler - söz verdikleri verimlilik kazanımlarını ve makul argümanların sahip olması şart değildir. yapıldıktan onlar sorun etki alanı hakkında aslında hinder geliştirme ve düşünme bunu başaramaz.


Stdio çekirdek katmanında soyutlanmış değil mi? Yanılmıyorsam, C-kütüphane onları karakter dosyaları / cihazları olarak ele alır ve çekirdek sürücüleri işi yapar, ...
kravemir

15

Hayvan ve Köpek ile önemsiz bir örnek: C ++ 'ın vtable mekanizmasını yansıtırsınız (büyük ölçüde her halükarda). Ayrıca ayırmayı ve somutlaştırmayı (Animal_Alloc, Animal_New) ayırırsınız, bu nedenle malloc () işlevini birden çok kez çağırmayız. thisİşaretçiyi de açıkça geçirmeliyiz .

Sanal olmayan işlevler yapacak olsaydınız, bu üç değerdir. Bunları vtable'a eklemezsiniz ve statik işlevler bir thisişaretçi gerektirmez . Çoklu kalıtım, belirsizlikleri çözmek için genellikle birden fazla veable gerektirir.

Ayrıca, kural dışı durum işleme için setjmp / longjmp komutunu kullanabilmeniz gerekir.

struct Animal_Vtable{
    typedef void (*Walk_Fun)(struct Animal *a_This);
    typedef struct Animal * (*Dtor_Fun)(struct Animal *a_This);

    Walk_Fun Walk;
    Dtor_Fun Dtor;
};

struct Animal{
    Animal_Vtable vtable;

    char *Name;
};

struct Dog{
    Animal_Vtable vtable;

    char *Name; // Mirror member variables for easy access
    char *Type;
};

void Animal_Walk(struct Animal *a_This){
    printf("Animal (%s) walking\n", a_This->Name);
}

struct Animal* Animal_Dtor(struct Animal *a_This){
    printf("animal::dtor\n");
    return a_This;
}

Animal *Animal_Alloc(){
    return (Animal*)malloc(sizeof(Animal));
}

Animal *Animal_New(Animal *a_Animal){
    a_Animal->vtable.Walk = Animal_Walk;
    a_Animal->vtable.Dtor = Animal_Dtor;
    a_Animal->Name = "Anonymous";
    return a_Animal;
}

void Animal_Free(Animal *a_This){
    a_This->vtable.Dtor(a_This);

    free(a_This);
}

void Dog_Walk(struct Dog *a_This){
    printf("Dog walking %s (%s)\n", a_This->Type, a_This->Name);
}

Dog* Dog_Dtor(struct Dog *a_This){
    // Explicit call to parent destructor
    Animal_Dtor((Animal*)a_This);

    printf("dog::dtor\n");

    return a_This;
}

Dog *Dog_Alloc(){
    return (Dog*)malloc(sizeof(Dog));
}

Dog *Dog_New(Dog *a_Dog){
    // Explict call to parent constructor
    Animal_New((Animal*)a_Dog);

    a_Dog->Type = "Dog type";
    a_Dog->vtable.Walk = (Animal_Vtable::Walk_Fun) Dog_Walk;
    a_Dog->vtable.Dtor = (Animal_Vtable::Dtor_Fun) Dog_Dtor;

    return a_Dog;
}

int main(int argc, char **argv){
    /*
      Base class:

        Animal *a_Animal = Animal_New(Animal_Alloc());
    */
    Animal *a_Animal = (Animal*)Dog_New(Dog_Alloc());

    a_Animal->vtable.Walk(a_Animal);

    Animal_Free(a_Animal);
}

PS. Bu bir C ++ derleyicisi üzerinde test edilmiştir, ancak bir C derleyicisi üzerinde çalışmasını sağlamak kolay olmalıdır.


typedefiçinde a içinde structmümkün değildir
masoud

13

GObject'e göz atın . C'de OO ve aradığınız şeyin bir uygulaması olması gerekiyordu. Gerçekten OO istiyorsanız, C ++ veya başka bir OOP dili ile gidin. Ob dilleri ile uğraşmaya alışkınsanız GObject zaman zaman çalışmak gerçekten zor olabilir, ancak her şey gibi, sözleşmelere ve akışa alışacaksınız.


12

Bunu okumak ilginçti. Ben de aynı soruyu kendim düşünmekteyim ve bunun üzerinde düşünmenin faydaları şunlardır:

  • OOP kavramlarının OOP olmayan bir dilde nasıl uygulanacağını hayal etmeye çalışmak OOp dilinin (benim durumumda C ++) güçlü yanlarını anlamama yardımcı olur. Bu, belirli bir uygulama türü için C veya C ++ kullanılıp kullanılmayacağına dair birinin daha iyi yargılanmasına yardımcı olur - birinin yararlarının diğerini aştığı.

  • Bu konuda bilgi ve görüşler için web'e göz attığımda, gömülü bir işlemci için kod yazan ve sadece bir C derleyicisi bulunan bir yazar buldum: http://www.eetimes.com/discussion/other/4024626/Object-Oriented C-oluşturma-Hazırlık-Sınıflar-Bölüm-1

Onun durumunda, düz C'deki OOP kavramlarını analiz etmek ve uyarlamak geçerli bir arayıştı. Görünüşe göre C'de uygulama girişiminden kaynaklanan performans havai vuruşundan dolayı bazı OOP kavramlarını feda etmeye açıktı.

Aldığım ders, evet belli bir dereceye kadar yapılabilir ve evet, bunu denemek için bazı iyi nedenler var.

Sonunda, makine yığın sayacı bitlerini döndürerek program sayacını atlatıyor ve bellek erişim işlemlerini hesaplıyor. Verimlilik açısından, bu hesaplamalar programınız tarafından ne kadar az yapılırsa o kadar iyidir ... ama bazen bu vergiyi ödemek zorundayız, böylece programımızı insan hatasına en az duyarlı hale getirecek şekilde düzenleyebiliriz. OOP dil derleyicisi her iki yönü de optimize etmeye çalışır. Programcı bu kavramların C gibi bir dilde uygulanmasında çok daha dikkatli olmalıdır.


10

Core Foundation API setleri için Apple'ın belgelerine bakmakta yardımcı olabilirsiniz. Saf bir C API'sıdır, ancak türlerin çoğu Objective-C nesne eşdeğerlerine köprülenir.

Objective-C'nin tasarımına bakmak da faydalı olabilir. Nesne sisteminin C işlevleri açısından tanımlanması, örneğin objc_msg_sendbir nesne üzerindeki bir yöntemi çağırmak için C ++ 'dan biraz farklıdır . Derleyici köşeli parantez sözdizimini bu işlev çağrılarına dönüştürür, bu yüzden bunu bilmek zorunda değilsiniz, ancak sorunuzu göz önünde bulundurarak kaputun altında nasıl çalıştığını öğrenmek yararlı olabilir.


10

Kullanılabilecek birkaç teknik vardır. En önemlisi, projenin nasıl bölüneceği. Projemizde .h dosyasında bildirilen bir arabirimi ve nesnenin bir .c dosyasında uygulanmasını sağlıyoruz. Önemli olan, .h dosyasını içeren tüm modüllerin yalnızca bir nesneyivoid * görmesidir ve .c dosyası yapının içlerini bilen tek modüldür.

Bir sınıf için böyle bir şey örnek olarak FOO adını veriyoruz:

.H dosyasında

#ifndef FOO_H_
#define FOO_H_

...
 typedef struct FOO_type FOO_type;     /* That's all the rest of the program knows about FOO */

/* Declaration of accessors, functions */
FOO_type *FOO_new(void);
void FOO_free(FOO_type *this);
...
void FOO_dosomething(FOO_type *this, param ...):
char *FOO_getName(FOO_type *this, etc);
#endif

C uygulama dosyası böyle bir şey olacaktır.

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

struct FOO_type {
    whatever...
};


FOO_type *FOO_new(void)
{
    FOO_type *this = calloc(1, sizeof (FOO_type));

    ...
    FOO_dosomething(this, );
    return this;
}

Bu yüzden işaretçiyi bir nesneye o modülün her fonksiyonuna açıkça veriyorum. Bir C ++ derleyicisi bunu örtük olarak yapar ve C'de açıkça yazarız.

Gerçekten kullanıyorum thisProgramlarımda C ++ ile derlenmediğinden emin olmak için programlarımda ve sözdizimi vurgulama düzenleyicimde başka bir renkte olma özelliğine sahip.

FOO_struct alanları bir modülde değiştirilebilir ve hala kullanılabilir olması için başka bir modülün yeniden derlenmesi gerekmez.

Bu stille zaten OOP (veri kapsülleme) avantajlarının büyük bir kısmını ele alıyorum. İşlev işaretçileri kullanarak, kalıtım gibi bir şeyi uygulamak bile kolaydır, ancak dürüst olmak gerekirse, gerçekten nadiren yararlıdır.


6
Bunu yaparsanız typedef struct FOO_type FOO_typebaşlığında geçersiz bir typedef yerine hala yapısını açığa değilken, tür denetleme yararı olsun.
Scott Wales

8

İşlev işaretçileri kullanarak taklit edebilirsiniz ve aslında, C ++ programlarını C'ye derlemek teorik olarak mümkün olduğunu düşünüyorum.

Ancak, paradigma kullanan bir dil seçmek yerine bir paradigmayı bir dile zorlamak nadiren mantıklıdır.


5
İlk C ++ derleyicisi tam olarak bunu yaptı - C ++ kodunu, daha sonra C derleyicisi tarafından derlenen eşdeğer (ancak çirkin ve insan tarafından okunamayan) C koduna dönüştürdü.
Adam Rosenfield

2
EDG, Cfront ve diğerleri hala bunu yapabilir. Çok iyi bir sebeple: her platformun bir C ++ derleyicisi yoktur.
Jasper Bekkers

Bazı nedenlerden ötürü C-front'in sadece belirli C ++ uzantılarını (örn. Referanslar) desteklediğini, ancak tam OOP / dinamik dağıtım emülasyonunu desteklemediğini düşündüm.
Uri

2
Aynı şeyi LLVM ve C arka ucuyla da yapabilirsiniz.
Zifre

7

Nesneye yönelik C, yapılabilir, Kore'de üretimde bu tür bir kod gördüm ve yıllar içinde gördüğüm en korkunç canavardı (bu kod geçen yıl (2007) gibiydi). Yani evet yapılabilir ve evet insanlar daha önce yapmışlardı ve hala bu gün ve yaşta bile yapıyorlar. Ancak C ++ veya Objective-C'yi öneririm, her ikisi de farklı paradigmalarla nesne yönelimi sağlamak amacıyla C'den doğan dillerdir.


3
Linus yorumunuzu görürse. Kesinlikle ya gülecek ya da lanet edecek
Anders Lind

7

Bir OOP yaklaşımının çözmeye çalıştığınız sorundan daha üstün olduğuna inanıyorsanız, neden OOP olmayan bir dille çözmeye çalışıyorsunuz? Görünüşe göre iş için yanlış aracı kullanıyorsunuz. C ++ veya başka bir nesne yönelimli C varyant dili kullanın.

C ile yazılmış zaten var olan büyük bir projeyi kodlamaya başladığınızı soruyorsanız, kendi (veya başka birinin) OOP paradigmalarını projenin altyapısına zorlamaya çalışmamalısınız. Projede zaten mevcut olan yönergeleri izleyin. Genel olarak, temiz API'ler ve izole kütüphaneler ve modüller temiz OOP- sahip yolunda uzun bir yol gidecek imsi tasarım.

Tüm bunlardan sonra, gerçekten OOP C yapmayı ayarlanmışsa, okumak bu (PDF).


36
Soruyu gerçekten cevaplamıyor ...
Brian Postow

2
@Brian, PDF bağlantısını doğrudan kendime kontrol etmek için vaktim olmasa da soruyu doğrudan yanıtlıyor gibi görünüyor.
Mark Ransom

5
PDF bağlantısı konuyla ilgili tüm bir ders kitabı gibi görünüyor ... Güzel bir kanıt, ancak kenar boşluğuna uymuyor ...
Brian Postow

5
evet, soruyu cevapla. bir dilin belirli bir şekilde nasıl kullanılacağını sormak kesinlikle geçerlidir. diğer diller hakkında görüş talebi yoktu ....
Tim Ring

9
@Brian & Tim Ring: Bir konuda kitap önerileri sorulan soru ; Ona özellikle bu konuyu ele alan bir kitabın bağlantısını verdim . Ayrıca soruna yaklaşımın neden en iyi olmayabileceğine dair fikrimi de verdim (buradaki birçok insanın oylara ve diğer yorumlara / cevaplara dayanarak aynı fikirde olduğunu düşünüyorum). Cevabımı geliştirmek için herhangi bir öneriniz var mı?
RarrRarrRarr

6

Evet yapabilirsin. İnsanlar C ++ veya Objective-C'den önce nesne yönelimli C yazıyordu sahneye . Hem C ++ hem de Objective-C, kısmen, C'de kullanılan OO kavramlarından bazılarını almaya ve bunları dilin bir parçası olarak resmileştirmeye çalışıyordu.

İşte benzeyen / bir yöntem çağrısı olan bir şeyi nasıl yapabileceğinizi gösteren gerçekten basit bir program (bunu yapmanın daha iyi yolları var. Bu sadece dilin kavramları desteklediğinin kanıtıdır):

#include<stdio.h>

struct foobarbaz{
    int one;
    int two;
    int three;
    int (*exampleMethod)(int, int);
};

int addTwoNumbers(int a, int b){
    return a+b;
}

int main()
{
    // Define the function pointer
    int (*pointerToFunction)(int, int) = addTwoNumbers;

    // Let's make sure we can call the pointer
    int test = (*pointerToFunction)(12,12);
    printf ("test: %u \n",  test);

    // Now, define an instance of our struct
    // and add some default values.
    struct foobarbaz fbb;
    fbb.one   = 1;
    fbb.two   = 2;
    fbb.three = 3;

    // Now add a "method"
    fbb.exampleMethod = addTwoNumbers;

    // Try calling the method
    int test2 = fbb.exampleMethod(13,36);
    printf ("test2: %u \n",  test2);

    printf("\nDone\n");
    return 0;
}

6

Tabii ki, yerleşik destekli bir dil kullanmak kadar güzel olmayacak. Hatta "nesne yönelimli montajcı" yazdım.


6

Eklenecek küçük bir OOC kodu:

#include <stdio.h>

struct Node {
    int somevar;
};

void print() {
    printf("Hello from an object-oriented C method!");
};

struct Tree {
    struct Node * NIL;
    void (*FPprint)(void);
    struct Node *root;
    struct Node NIL_t;
} TreeA = {&TreeA.NIL_t,print};

int main()
{
    struct Tree TreeB;
    TreeB = TreeA;
    TreeB.FPprint();
    return 0;
}

5

Bunu bir yıldır kazıyorum:

GObject sisteminin saf C ile kullanımı zor olduğundan, C ile OO stilini hafifletmek için bazı güzel makrolar yazmaya çalıştım.

#include "OOStd.h"

CLASS(Animal) {
    char *name;
    STATIC(Animal);
    vFn talk;
};
static int Animal_load(Animal *THIS,void *name) {
    THIS->name = name;
    return 0;
}
ASM(Animal, Animal_load, NULL, NULL, NULL)

CLASS_EX(Cat,Animal) {
    STATIC_EX(Cat, Animal);
};
static void Meow(Animal *THIS){
    printf("Meow!My name is %s!\n", THIS->name);
}

static int Cat_loadSt(StAnimal *THIS, void *PARAM){
    THIS->talk = (void *)Meow;
    return 0;
}
ASM_EX(Cat,Animal, NULL, NULL, Cat_loadSt, NULL)


CLASS_EX(Dog,Animal){
    STATIC_EX(Dog, Animal);
};

static void Woof(Animal *THIS){
    printf("Woof!My name is %s!\n", THIS->name);
}

static int Dog_loadSt(StAnimal *THIS, void *PARAM) {
    THIS->talk = (void *)Woof;
    return 0;
}
ASM_EX(Dog, Animal, NULL, NULL, Dog_loadSt, NULL)

int main(){
    Animal *animals[4000];
    StAnimal *f;
    int i = 0;
    for (i=0; i<4000; i++)
    {
        if(i%2==0)
            animals[i] = NEW(Dog,"Jack");
        else
            animals[i] = NEW(Cat,"Lily");
    };
    f = ST(animals[0]);
    for(i=0; i<4000; ++i) {
        f->talk(animals[i]);
    }
    for (i=0; i<4000; ++i) {
        DELETE0(animals[i]);
    }
    return 0;
}

İşte benim proje sitesi (en. Doc yazmak için yeterli zamanım yok, ancak çince doktor çok daha iyi).

OOC-GCC


SINIF STATİK ASM YENİ ST SİL ... OOC-GCC makrolardır
Dameng


4

Hangi makaleler veya kitaplar C'de OOP kavramlarını kullanmakta iyidir?

Dave Hanson'un Arayüzler ve gerçekleştirmeler olan mükemmel kapsülleme ve adlandırma ve işlev işaretçileri kullanımı konusunda çok iyi. Dave mirası simüle etmeye çalışmaz.


4

OOP sadece verileri programlarda koddan daha önemli kılan bir paradigmadır. OOP bir dil değil. Yani, düz C basit bir dil gibi, düz C'deki OOP da basittir.


3
İyi dedi, ama bu yorum olmalı.
pqsk

4

Yapmak isteyebileceğiniz bir şey , X Window için Xt araç setinin uygulanmasına bakmaktır . Elbette dişte uzun sürüyor, ancak kullanılan yapıların çoğu geleneksel C içinde bir OO tarzında çalışacak şekilde tasarlandı.

C'de bu şekilde yer alan OO yolunda gerçekten çok şey yapabilirsiniz, bazı zamanlarda hissettirse de, OO kavramları zihninden tam olarak oluşmadı #include<favorite_OO_Guru.h>. Gerçekten de zamanın en iyi uygulamalarını oluşturdular. OO dilleri ve sistemleri sadece günün programlama zeitgeist damıtılmış ve güçlendirilmiş bölümleri.


4

Sorunun cevabı 'Evet, yapabilirsiniz'.

Nesne yönelimli C (OOC) kiti, nesne yönelimli bir şekilde programlamak isteyenler içindir, ancak eski C'ye de yapışır. OOC sınıfları, tekli ve çoklu kalıtım, istisna yönetimi uygular.

Özellikleri

• Yalnızca C makroları ve işlevleri kullanır, dil uzantısı gerekmez! (ANSI-C)

• Uygulamanız için okunması kolay kaynak kodu. İşleri mümkün olduğunca basit hale getirmeye özen gösterildi.

• Sınıfların tek mirası

• Arayüzler ve karışımlar ile çoklu kalıtım (sürüm 1.3'ten beri)

• İstisnaları uygulama (saf C'de!)

• Sınıflar için sanal fonksiyonlar

• Kolay sınıf uygulaması için harici araç

Daha fazla ayrıntı için http://ooc-coding.sourceforge.net/ adresini ziyaret edin .


4

Görünen o ki insanlar C kullanarak C ++ stilini taklit etmeye çalışıyorlar. Ancak geç bağlama, kapsülleme ve kalıtım gibi şeyleri başarabilirsiniz. Kalıtım için, alt yapınızdaki temel yapılara açıkça bir işaretçi tanımlarsınız ve bu açıkça çoklu kalıtım biçimidir. Ayrıca,

//private_class.h
struct private_class;
extern struct private_class * new_private_class();
extern int ret_a_value(struct private_class *, int a, int b);
extern void delete_private_class(struct private_class *);
void (*late_bind_function)(struct private_class *p);

//private_class.c
struct inherited_class_1;
struct inherited_class_2;

struct private_class {
  int a;
  int b;
  struct inherited_class_1 *p1;
  struct inherited_class_2 *p2;
};

struct inherited_class_1 * new_inherited_class_1();
struct inherited_class_2 * new_inherited_class_2();

struct private_class * new_private_class() {
  struct private_class *p;
  p = (struct private_class*) malloc(sizeof(struct private_class));
  p->a = 0;
  p->b = 0;
  p->p1 = new_inherited_class_1();
  p->p2 = new_inherited_class_2();
  return p;
}

    int ret_a_value(struct private_class *p, int a, int b) {
      return p->a + p->b + a + b;
    }

    void delete_private_class(struct private_class *p) {
      //release any resources
      //call delete methods for inherited classes
      free(p);
    }
    //main.c
    struct private_class *p;
    p = new_private_class();
    late_bind_function = &implementation_function;
    delete_private_class(p);

ile derlemek c_compiler main.c inherited_class_1.obj inherited_class_2.obj private_class.obj.

Bu yüzden tavsiye, saf bir C stiline bağlı kalmak ve bir C ++ stiline zorlamaya çalışmamaktır. Ayrıca bu şekilde bir API oluşturmanın çok temiz bir yoluna sahiptir.


Kalıtım için tipik olarak temel sınıf veya örnek yapısı türetilmiş yapıya gömülür, ayrı olarak tahsis edilmez ve işaretçiler kullanılarak refere edilir. Bu şekilde, en üstteki taban her zaman türetilen yapıların herhangi birinin başlangıcında olur, böylece birbirlerine herhangi bir ofsette olabilecek işaretçilerle yapamayacağınız kolaylıkla atılabilirler.
underscore_d

2

Bkz http://slkpg.byethost7.com/instance.html elle fonksiyon sarmalayıcılarını aracılığıyla kontrol edilir sadece yerli C. Çoklu miras kullanarak reentrancy için örnek verileri vurgulayan C. OOP başka büküm henüz için. Tip güvenliği sağlanır. İşte küçük bir örnek:

typedef struct _peeker
{
    log_t     *log;
    symbols_t *sym;
    scanner_t  scan;            // inherited instance
    peek_t     pk;
    int        trace;

    void    (*push) ( SELF *d, symbol_t *symbol );
    short   (*peek) ( SELF *d, int level );
    short   (*get)  ( SELF *d );
    int     (*get_line_number) ( SELF *d );

} peeker_t, SlkToken;

#define push(self,a)            (*self).push(self, a)
#define peek(self,a)            (*self).peek(self, a)
#define get(self)               (*self).get(self)
#define get_line_number(self)   (*self).get_line_number(self)

INSTANCE_METHOD
int
(get_line_number) ( peeker_t *d )
{
    return  d->scan.line_number;
}

PUBLIC
void
InitializePeeker ( peeker_t  *peeker,
                   int        trace,
                   symbols_t *symbols,
                   log_t     *log,
                   list_t    *list )
{
    InitializeScanner ( &peeker->scan, trace, symbols, log, list );
    peeker->log = log;
    peeker->sym = symbols;
    peeker->pk.current = peeker->pk.buffer;
    peeker->pk.count = 0;
    peeker->trace = trace;

    peeker->get_line_number = get_line_number;
    peeker->push = push;
    peeker->get = get;
    peeker->peek = peek;
}

2

Partiye biraz geç kaldım, ancak konuyla ilgili deneyimlerimi paylaşmak istiyorum: Bu günlerde gömülü öğelerle çalışıyorum ve sahip olduğum tek (güvenilir) derleyici C, böylece nesne odaklı uygulamak istiyorum dilinde yazılmış gömülü projelerimde yaklaşım

Şimdiye kadar gördüğüm çözümlerin çoğu, tipik olarak daktiloları kullanıyor, bu nedenle tip güvenliğini kaybediyoruz: hata yaparsanız derleyici size yardımcı olmaz. Bu tamamen kabul edilemez.

Sahip olduğum gereksinimler:

  • Yazım kurallarından mümkün olduğunca kaçının, bu nedenle tür güvenliğini kaybetmeyiz;
  • Çok biçimlilik: sanal yöntemleri kullanabilmeliyiz ve sınıfın kullanıcısı belirli bir yöntemin sanal olup olmadığının farkında olmamalıdır;
  • Çoklu kalıtım: Sık kullanmıyorum, ancak bazen gerçekten bazı sınıfların birden çok arabirim (veya birden çok üst sınıfı genişletmek) gerçekleştirmesini istiyorum.

Bu makalede yaklaşımımı ayrıntılı olarak açıkladım: C dilinde nesne yönelimli programlama ; ayrıca, temel ve türetilmiş sınıflar için kazan plakası kodunun otomatik üretimi için bir yardımcı program vardır.


2

Bunu denediğim küçük bir kütüphane inşa ettim ve bana göre çok iyi çalışıyor. Deneyimi paylaştığımı düşündüm.

https://github.com/thomasfuhringer/oxygen

Tek miras, bir yapı kullanılarak ve diğer tüm çocuk sınıfları için genişletilerek kolayca uygulanabilir. Ana yapıya basit bir döküm, tüm torunlarda ana yöntemlerin kullanılmasını mümkün kılar. Bir değişkenin bu tür bir nesneyi tutan bir yapıya işaret ettiğini bildiğiniz sürece, her zaman kök sınıfına atabilir ve içgözlem yapabilirsiniz.

Daha önce de belirtildiği gibi, sanal yöntemler biraz daha zordur. Ama yapılabilirler. İşleri basit tutmak için, sınıf tanımlama yapısında, her alt sınıfın gerektiğinde ayrı yuvaları kopyalayıp yeniden doldurduğu bir dizi işlev kullanıyorum.

Çoklu kalıtımın uygulanması oldukça karmaşıktır ve önemli bir performans etkisi ile birlikte gelir. Bu yüzden bırakıyorum. Birkaç durumda gerçek yaşam koşullarını temiz bir şekilde modellemenin arzu edilir ve yararlı olduğunu düşünüyorum, ancak vakaların% 90'ında tek miras ihtiyaçları karşılamaktadır. Ve tek miras basittir ve hiçbir maliyeti yoktur.

Ayrıca tip güvenliği de umurumda değil. Programlama hatalarını önlemek için derleyiciye güvenmemeniz gerektiğini düşünüyorum. Ve yine de sizi hataların sadece küçük bir kısmından korur.

Genellikle, nesne yönelimli bir ortamda, bellek yönetimini mümkün olduğunca otomatikleştirmek için referans sayımı uygulamak istersiniz. Bu yüzden "Nesne" kök sınıfına bir referans sayımı ve yığın belleğin tahsisini ve yeniden yerleştirilmesini kapsüllemek için bazı işlevler koydum.

Her şey çok basit ve yalın ve beni C ++ olan canavarla uğraşmaya zorlamadan OO'nun temellerini veriyor. Ve diğer şeylerin yanı sıra üçüncü taraf kütüphanelerini entegre etmeyi kolaylaştıran C topraklarında kalma esnekliğini koruyorum.


1

C'nin bir üst kümesi olan Objective-C'yi kullanmayı öneriyorum.

Objective-C 30 yaşındayken zarif kod yazmanıza izin verir.

http://en.wikipedia.org/wiki/Objective-C


Bu durumda aslında nesne odaklı beri C ++ tavsiye ederim ...
yyny

Bu bir cevap değil. Her neyse, @YoYoYonnY: Objective-C kullanmıyorum ve C ++ kullanmıyorum, ancak bunun gibi yorumlar temelsiz olarak kullanılmıyor ve siz hiçbir şey sağlamadınız. Neden Objective-C'nin "aslında nesne yönelimli ..." olmaktan mahrum olduğunu iddia ediyorsunuz? Ve neden C ++, Objective-C'nin başarısız olduğu yerde başarılı olur? Komik olan şey, Objective-C'nin kelimenin tam anlamıyla kendi adına Object kelimesine sahip olmasıdır , oysa C ++ kendisini bir OOP dili değil (yani öncelikle OOP değil ve bazı aşırı halk bakış açısına göre OOP değil) çok paradigma dili olarak pazarlamaktadır. ... bu isimleri yanlış bir şekilde anlamadığınızdan emin misiniz?
underscore_d

0

Evet, ama hiç kimsenin C ile herhangi bir çeşit polimorfizm uygulamaya çalıştığını görmedim.


6
Daha fazla etrafa bakmanız gerekiyor :) Örneğin, Microsoft'un Direct X'inde polimorfik bir C arayüzü var.
19:

8
Örneğin Linux çekirdeği uygulamasına bakın. C'de çok yaygın ve yaygın olarak kullanılan bir uygulamadır.
Ilya

3
Ayrıca glib polimorfik veya polimorfizme izin veren bir şekilde kullanılabilir (C ++ gibi, hangi çağrıların sanal olduğunu açıkça söylemelisiniz)
Spudd86

1
Polimorfizm C'de nadir değildir, diğer yandan çoklu kalıtımdır.
Johan Bjäreholt
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.