Wie würde man objektorientierten Code in C schreiben? [geschlossen]

500

Wie kann man objektorientierten Code in C schreiben? Besonders im Hinblick auf Polymorphismus.


Siehe auch diese Stack - Überlauf Frage Objektorientierung in C .

Peter Mortensen
quelle
1
<a href=" ldeniau.web.cern.ch/ldeniau/html/oopc.html"> Objektorientierte Programmierung in C </a> von Laurent Deniau
Vielleicht möchten Sie sich die Antworten auf diese Frage ansehen: Welche Techniken / Strategien verwenden Benutzer zum Erstellen von Objekten in C (nicht in C ++)?
Dale Hagglund
25
@Camilo Martin: Ich habe absichtlich gefragt, kann nicht sollte . Ich bin eigentlich nicht daran interessiert, OOP in C zu verwenden. Wenn ich jedoch OO-Lösungen in C sehe, kann ich / wir mehr über die Grenzen und / oder die Flexibilität von C und auch über kreative Möglichkeiten zur Implementierung und Verwendung von Polymorphismus erfahren.
Dinah
5
OO ist nur ein Muster. Überprüfen Sie hier, es kann sogar in .bat-Dateien durchgeführt werden: dirk.rave.org/chap9.txt (jedes Muster kann auf jede Programmiersprache angewendet werden, wenn Sie interessiert genug sind, denke ich). Dies ist jedoch ein guter Denkanstoß. Und wahrscheinlich kann man viel lernen, wenn man solche Muster auf Sprachen anwendet, die sie nicht haben.
Camilo Martin
6
GTK - entschuldigen Sie, GObject - ist eigentlich ein ziemlich gutes Beispiel für OOP (sorta) in C. Um @Camilo für C-Interpolierbarkeit zu beantworten.
New123456

Antworten:

362

Ja. Tatsächlich stellt Axel Schreiner sein Buch "Objektorientierte Programmierung in ANSI-C" kostenlos zur Verfügung, das das Thema ziemlich ausführlich behandelt.

Mepcotterell
quelle
28
Während die Konzepte in diesem Buch solide sind, verlieren Sie die Typensicherheit.
Diapir
22
Vor dem, was wir als Entwurfsmuster kennen, war das Entwurfsmuster als "Objektorientierung" bekannt; das gleiche gilt für die Speicherbereinigung und andere solche. Sie sind jetzt so tief verwurzelt, dass wir oft vergessen, dass sie, als sie zum ersten Mal entwickelt wurden, ähnlich waren wie das, was wir heute als Designmuster betrachten
Dexygen
11
Sie können es direkt von der Website des Autors erhalten: cs.rit.edu/~ats/books/ooc.pdf andere Artikel desselben Autors: cs.rit.edu/~ats/books/index.html
pakman
10
Die richtige Sammlung (Buch + Quellcode-Beispiele) finden Sie in diesem rit.edu-Index. Objektorientierte Programmierung mit ANSI-C
David C. Rankin
3
Ist dieses Buch von Experten begutachtet? Im ersten Satz des ersten Absatzes der ersten Seite befindet sich ein Tippfehler.
Dagrooms
343

Da Sie dann über Polymorphismus sprechen, haben wir solche Dinge Jahre vor C ++ gemacht.

Grundsätzlich verwenden Sie a struct, um sowohl die Daten als auch eine Liste von Funktionszeigern zu speichern, um auf die relevanten Funktionen für diese Daten zu verweisen.

In einer Kommunikationsklasse hätten Sie also einen Aufruf zum Öffnen, Lesen, Schreiben und Schließen, der als vier Funktionszeiger in der Struktur neben den Daten für ein Objekt beibehalten wird, etwa:

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;

Natürlich würden sich diese obigen Codesegmente tatsächlich in einem "Konstruktor" wie z rs232Init().

Wenn Sie von dieser Klasse 'erben', ändern Sie einfach die Zeiger, um auf Ihre eigenen Funktionen zu verweisen. Jeder, der diese Funktionen aufgerufen hat, würde dies über die Funktionszeiger tun und Ihnen Ihren Polymorphismus geben:

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

So ähnlich wie eine manuelle Tabelle.

Sie könnten sogar virtuelle Klassen haben, indem Sie die Zeiger auf NULL setzen. Das Verhalten würde sich geringfügig von C ++ unterscheiden (ein Core-Dump zur Laufzeit anstelle eines Fehlers zur Kompilierungszeit).

Hier ist ein Beispielcode, der dies demonstriert. Zuerst die Klassenstruktur der obersten Ebene:

#include <stdio.h>

// The top-level class.

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

Dann haben wir die Funktionen für die TCP-Unterklasse:

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

Und das HTTP auch:

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

Und zum Schluss ein Testprogramm, um es in Aktion zu zeigen:

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

Dies erzeugt die Ausgabe:

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

So können Sie sehen, dass die verschiedenen Funktionen abhängig von der Unterklasse aufgerufen werden.

paxdiablo
quelle
52
Die Einkapselung ist ziemlich einfach, Polymorphismus ist machbar - aber Vererbung ist schwierig
Martin Beckett
5
lwn.net hat kürzlich einen Artikel mit dem Titel Objektorientierte Entwurfsmuster im Kernel zum Thema Stucts veröffentlicht, die der obigen Antwort ähneln - dh eine Struktur, die Funktionszeiger enthält, oder einen Zeiger auf eine Struktur, die Funktionen enthält, die einen Zeiger auf die Struktur mit den Daten, mit denen wir arbeiten, als Parameter.
radikalmatt
11
+1 Schönes Beispiel! Wenn jemand diesen Weg wirklich gehen möchte, ist es für "Instanz" -Strukturen besser geeignet, ein einzelnes Feld zu haben , das auf seine "virtuelle Tabellen" -Instanz verweist und alle virtuellen Funktionen für diesen Typ an einer Stelle enthält. Das heißt, Sie tCommClasswürden in umbenannt tCommVT, und eine tCommClassStruktur hätte nur Datenfelder und ein einzelnes tCommVT vtFeld, das auf die "einzige" virtuelle Tabelle verweist. Das Mitführen aller Zeiger mit jeder Instanz erhöht den unnötigen Overhead und ähnelt eher der Vorgehensweise in JavaScript als in C ++, IMHO.
Groo
1
Dies zeigt also die Implementierung einer einzelnen Schnittstelle, aber was ist mit der Implementierung mehrerer Schnittstellen? Oder Mehrfachvererbung?
weberc2
Weber, wenn Sie alle Funktionen von C ++ nutzen möchten, sollten Sie wahrscheinlich C ++ verwenden. Die Frage bezog sich speziell auf den Polymorphismus, die Fähigkeit von Objekten, eine andere "Form" anzunehmen. Sie können in C sicherlich Schnittstellen und Mehrfachvererbung ausführen, aber es ist einiges an zusätzlicher Arbeit, und Sie müssen die Smarts selbst verwalten, anstatt C ++ -Einbauten zu verwenden.
Paxdiablo
86

Namespaces werden häufig folgendermaßen erstellt:

stack_push(thing *)

anstatt

stack::push(thing *)

Um eine C- Struktur in eine C ++ - Klasse zu verwandeln, können Sie Folgendes tun:

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

In

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

Und TU:

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
}

Ich habe den Destruktor nicht ausgeführt oder gelöscht, aber er folgt demselben Muster.

this_is_here_as_an_example_only ist wie eine statische Klassenvariable, die von allen Instanzen eines Typs gemeinsam genutzt wird. Alle Methoden sind wirklich statisch, außer dass einige dies * annehmen

nategoose
quelle
1
@nategoose - st->my_type->push(st, thing2);anstelle vonst->my_type.push(st, thing2);
Fabricio
@nategoose: ODER struct stack_type my_type; stattstruct stack_type * my_type;
Fabricio
3
Ich mag das Konzept, eine Struktur für die Klasse zu haben. Aber wie wäre es mit einer generischen ClassStruktur? Das wäre der OO C macht mehr dynamischer als C ++. Wie ist es damit? Übrigens +1.
Linuxios
54

Ich glaube, dass die Implementierung von OOP in C nicht nur für sich genommen nützlich ist, sondern auch eine hervorragende Möglichkeit ist , OOP zu lernen und sein Innenleben zu verstehen. Die Erfahrung vieler Programmierer hat gezeigt, dass ein Programmierer verstehen muss, wie die zugrunde liegenden Konzepte letztendlich implementiert werden, um eine Technik effizient und sicher einzusetzen. Das Emulieren von Klassen, Vererbung und Polymorphismus in C lehrt genau dies.

Um die ursprüngliche Frage zu beantworten, finden Sie hier einige Ressourcen, die zeigen, wie OOP in C ausgeführt wird:

Der Blog-Beitrag von EmbeddedGurus.com "Objektbasierte Programmierung in C" zeigt, wie Klassen und Einzelvererbung in portablem C implementiert werden: http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c /.

Anwendungshinweis "" C + "- Objektorientierte Programmierung in C" zeigt, wie Klassen, Einzelvererbung und späte Bindung (Polymorphismus) in C mithilfe von Präprozessor-Makros implementiert werden: http://www.state-machine.com/resources/cplus_3. 0_manual.pdf , der Beispielcode ist unter http://www.state-machine.com/resources/cplus_3.0.zip verfügbar

Miro
quelle
4
Neue URL für das C + Handbuch: state-machine.com/doc/cplus_3.0_manual.pdf
Liang
32

Ich habe es gesehen. Ich würde es nicht empfehlen. C ++ begann ursprünglich auf diese Weise als Präprozessor, der C-Code als Zwischenschritt erzeugte.

Im Wesentlichen erstellen Sie am Ende eine Versandtabelle für alle Ihre Methoden, in der Sie Ihre Funktionsreferenzen speichern. Das Ableiten einer Klasse würde das Kopieren dieser Versandtabelle und das Ersetzen der Einträge erfordern, die Sie überschreiben möchten, wobei Ihre neuen "Methoden" die ursprüngliche Methode aufrufen müssen, wenn sie die Basismethode aufrufen möchten. Schließlich schreiben Sie C ++ neu.

Tvanfosson
quelle
5
"Schließlich schreiben Sie C ++ neu." Ich fragte mich, ob / befürchtete, dass dies der Fall sein würde.
Dinah
39
Oder Sie schreiben möglicherweise Ziel C neu, was ein viel attraktiveres Ergebnis wäre.
Prof. Falken Vertrag verletzt
3
Es gibt den klassenlosen Geschmack von OOP, wie in Javascript , wo der Guru sagt: "Wir brauchen keine Klassen, um viele ähnliche Objekte zu erstellen." Aber ich befürchte, dass dies in C nicht leicht zu erreichen ist. Allerdings (noch) nicht in der Lage zu sagen. (Gibt es eine clone () Routine, um eine Struktur zu klonen?)
Lumi
1
Ein anderer kluger Kerl, der das tatsächlich implementieren und diese Implementierung schnell machen musste (Google, V8-Engine), hat alles getan, um JavaScript (versteckte) Klassen wieder hinzuzufügen.
cubuspl42
Ist nicht glibobjektiv in C geschrieben?
Kravemir
26

Sicher ist das möglich. Dies ist, was GObject , das Framework, auf dem alle GTK + und GNOME basieren, tut.

Peter Mortensen
quelle
Was sind die Vor- und Nachteile eines solchen Ansatzes? Dh. Es ist viel einfacher, es mit C ++ zu schreiben.
Kravemir
@kravemir Nun, C ++ ist nicht ganz so portabel wie C, und es ist etwas schwieriger, C ++ mit Code zu verknüpfen, der möglicherweise von einem anderen C ++ - Compiler kompiliert wurde. Aber ja, es ist einfacher, Klassen in C ++ zu schreiben, obwohl GObject auch nicht wirklich so schwierig ist (vorausgesetzt, Sie haben nichts gegen eine kleine Kesselplatte).
Edwin Buck
17

Die C stdio FILE-Unterbibliothek ist ein hervorragendes Beispiel für die Erstellung von Abstraktion, Kapselung und Modularität in unverfälschtem C.

Vererbung und Polymorphismus - die anderen Aspekte, die häufig als wesentlich für OOP angesehen werden - liefern nicht unbedingt die versprochenen Produktivitätsgewinne, und es wurden vernünftige Argumente dafür vorgebracht, dass sie die Entwicklung und das Nachdenken über den Problembereich tatsächlich behindern können.

msw
quelle
Ist stdio nicht auf der Kernel-Ebene abstrahiert? Wenn ich mich nicht irre, behandelt die C-Bibliothek sie als Zeichendateien / Geräte, und
Kerneltreiber
15

Triviales Beispiel mit einem Tier und einem Hund: Sie spiegeln den vtable-Mechanismus von C ++ wider (größtenteils sowieso). Sie trennen auch Zuordnung und Instanziierung (Animal_Alloc, Animal_New), damit wir malloc () nicht mehrmals aufrufen. Wir müssen den thisZeiger auch explizit weitergeben .

Wenn Sie nicht virtuelle Funktionen ausführen, ist das ein Trival. Sie fügen sie einfach nicht zur vtable hinzu und für statische Funktionen ist kein thisZeiger erforderlich . Für die Mehrfachvererbung sind im Allgemeinen mehrere vtables erforderlich, um Mehrdeutigkeiten aufzulösen.

Außerdem sollten Sie in der Lage sein, setjmp / longjmp für die Ausnahmebehandlung zu verwenden.

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. Dies wird auf einem C ++ - Compiler getestet, es sollte jedoch einfach sein, es auf einem C-Compiler zum Laufen zu bringen.

Jasper Bekkers
quelle
typedefinnerhalb von a structist in C. nicht möglich
masoud
13

Schauen Sie sich GObject an . Es soll OO in C sein und eine Implementierung dessen, wonach Sie suchen. Wenn Sie jedoch wirklich OO möchten, wählen Sie C ++ oder eine andere OOP-Sprache. Es kann manchmal sehr schwierig sein, mit GObject zu arbeiten, wenn Sie es gewohnt sind, mit OO-Sprachen umzugehen, aber wie alles andere werden Sie sich an die Konventionen und den Ablauf gewöhnen.

NG.
quelle
12

Das war interessant zu lesen. Ich habe selbst über dieselbe Frage nachgedacht, und die Vorteile des Nachdenkens sind folgende:

  • Der Versuch, mir vorzustellen, wie OOP-Konzepte in einer Nicht-OOP-Sprache implementiert werden, hilft mir, die Stärken der OOp-Sprache (in meinem Fall C ++) zu verstehen. Dies hilft mir, besser zu beurteilen, ob ich C oder C ++ für einen bestimmten Anwendungstyp verwenden soll - wobei die Vorteile des einen den anderen überwiegen.

  • Beim Surfen im Internet nach Informationen und Meinungen dazu fand ich einen Autor, der Code für einen eingebetteten Prozessor schrieb und nur einen C-Compiler zur Verfügung hatte: http://www.eetimes.com/discussion/other/4024626/Object-Oriented -C-Erstellen-Foundation-Klassen-Teil-1

In seinem Fall war die Analyse und Anpassung von OOP-Konzepten in Ebene C eine gültige Aufgabe. Es scheint, dass er offen dafür war, einige OOP-Konzepte zu opfern, da der Leistungsaufwand durch den Versuch, sie in C zu implementieren, beeinträchtigt wurde.

Die Lektion, die ich genommen habe, ist: Ja, es kann bis zu einem gewissen Grad getan werden, und ja, es gibt einige gute Gründe, es zu versuchen.

Am Ende dreht die Maschine Stapelzeigerbits, wodurch der Programmzähler herumspringt und Speicherzugriffsoperationen berechnet werden. Unter dem Gesichtspunkt der Effizienz ist es umso besser, je weniger Berechnungen von Ihrem Programm durchgeführt werden. Manchmal müssen wir diese Steuer jedoch einfach zahlen, damit wir unser Programm so organisieren können, dass es am wenigsten anfällig für menschliches Versagen ist. Der OOP-Sprachcompiler ist bestrebt, beide Aspekte zu optimieren. Der Programmierer muss diese Konzepte in einer Sprache wie C viel sorgfältiger umsetzen.

RJB
quelle
10

Es kann hilfreich sein, die Dokumentation von Apple zu den Core Foundation-APIs zu lesen. Es ist eine reine C-API, aber viele der Typen sind mit Objective-C-Objektäquivalenten verbunden.

Es kann auch hilfreich sein, sich das Design von Objective-C selbst anzusehen. Es unterscheidet sich ein wenig von C ++ darin, dass das Objektsystem in Bezug auf C-Funktionen definiert ist, z. B. objc_msg_sendum eine Methode für ein Objekt aufzurufen. Der Compiler übersetzt die Syntax in eckigen Klammern in diese Funktionsaufrufe, sodass Sie sie nicht kennen müssen. Wenn Sie jedoch Ihre Frage berücksichtigen, ist es möglicherweise hilfreich, zu erfahren, wie sie unter der Haube funktioniert.

Benzado
quelle
10

Es gibt verschiedene Techniken, die verwendet werden können. Das wichtigste ist mehr, wie man das Projekt aufteilt. Wir verwenden in unserem Projekt eine Schnittstelle, die in einer .h-Datei deklariert ist, und die Implementierung des Objekts in einer .c-Datei. Der wichtige Teil ist, dass alle Module, die die .h-Datei enthalten, nur ein Objekt als sehen void *, und die .c-Datei das einzige Modul ist, das die Interna der Struktur kennt.

So etwas für eine Klasse nennen wir FOO als Beispiel:

In der .h-Datei

#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

Die C-Implementierungsdatei wird ungefähr so ​​sein.

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

Daher gebe ich den Zeiger explizit auf ein Objekt für jede Funktion dieses Moduls. Ein C ++ - Compiler macht das implizit und in C schreiben wir es explizit aus.

Ich verwende es wirklich thisin meinen Programmen, um sicherzustellen, dass mein Programm nicht in C ++ kompiliert wird, und es hat die feine Eigenschaft, in meinem Syntaxhervorhebungseditor in einer anderen Farbe zu sein.

Die Felder des FOO_struct können in einem Modul geändert werden, und ein anderes Modul muss nicht einmal neu kompiliert werden, um weiterhin verwendet werden zu können.

Mit diesem Stil beschäftige ich mich bereits mit einem großen Teil der Vorteile von OOP (Datenkapselung). Durch die Verwendung von Funktionszeigern ist es sogar einfach, so etwas wie Vererbung zu implementieren, aber ehrlich gesagt ist es wirklich nur selten nützlich.

Patrick Schlüter
quelle
6
Wenn Sie typedef struct FOO_type FOO_typeanstelle eines typedef den Header ungültig machen, erhalten Sie den zusätzlichen Vorteil der Typprüfung, ohne Ihre Struktur verfügbar zu machen.
Scott Wales
8

Sie können es mit Funktionszeigern fälschen, und tatsächlich denke ich, dass es theoretisch möglich ist, C ++ - Programme in C zu kompilieren.

Es ist jedoch selten sinnvoll, einer Sprache ein Paradigma aufzuzwingen, anstatt eine Sprache auszuwählen, die ein Paradigma verwendet.

Uri
quelle
5
Der allererste C ++ - Compiler hat genau das getan - er hat den C ++ - Code in äquivalenten (aber hässlichen und nicht für Menschen lesbaren) C-Code konvertiert, der dann vom C-Compiler kompiliert wurde.
Adam Rosenfield
2
EDG, Cfront und einige andere sind dazu noch in der Lage. Mit einem sehr guten Grund: Nicht jede Plattform verfügt über einen C ++ - Compiler.
Jasper Bekkers
Aus irgendeinem Grund dachte ich, dass C-Front nur bestimmte C ++ - Erweiterungen (z. B. Referenzen) unterstützt, aber keine vollständige OOP / Dynamic Dispatch-Emulation.
Uri
2
Sie können dasselbe auch mit LLVM und dem C-Backend tun.
Zifre
7

Objektorientiertes C, kann gemacht werden, ich habe diese Art von Code in der Produktion in Korea gesehen, und es war das schrecklichste Monster, das ich seit Jahren gesehen habe (dies war wie letztes Jahr (2007), als ich den Code gesehen habe). Also ja, es kann getan werden, und ja, die Leute haben es schon einmal getan und tun es auch heute noch. Aber ich würde C ++ oder Objective-C empfehlen, beide sind aus C geborene Sprachen, um der Objektorientierung unterschiedliche Paradigmen zu bieten.

Robert Gould
quelle
3
wenn Linus deinen Kommentar sieht. Er wird dich definitiv entweder lachen oder verfluchen
Anders Lind
7

Wenn Sie davon überzeugt sind, dass ein OOP-Ansatz für das Problem, das Sie lösen möchten, überlegen ist, warum sollten Sie versuchen, ihn mit einer Nicht-OOP-Sprache zu lösen? Anscheinend verwenden Sie das falsche Tool für den Job. Verwenden Sie C ++ oder eine andere objektorientierte C-Variantensprache.

Wenn Sie fragen, weil Sie mit dem Codieren eines bereits vorhandenen großen Projekts in C beginnen, sollten Sie nicht versuchen, Ihre eigenen (oder die anderer) OOP-Paradigmen in die Infrastruktur des Projekts zu zwingen. Befolgen Sie die Richtlinien, die bereits im Projekt vorhanden sind. Im Allgemeinen sauber APIs und isoliert Bibliotheken und Module werden einen langen Weg in Richtung mit einem sauberen OOP- gehen ish Designs.

Wenn Sie nach all dem wirklich auf OOP C eingestellt sind, lesen Sie dies (PDF).

RarrRarrRarr
quelle
36
Die Frage nicht wirklich beantworten ...
Brian Postow
2
@Brian, der Link zum PDF scheint die Frage direkt zu beantworten, obwohl ich keine Zeit hatte, es selbst zu überprüfen.
Mark Ransom
5
Der Link zum PDF scheint ein ganzes Lehrbuch zu diesem Thema zu sein ... Ein schöner Beweis, aber er passt nicht in den Rand ...
Brian Postow
5
Ja, beantworte die Frage. Es ist absolut richtig zu fragen, wie man eine Sprache auf eine bestimmte Weise benutzt. Es gab keine Anfrage für Meinungen zu anderen Sprachen ....
Tim Ring
9
@ Brian & Tim Ring: Die Frage nach Buchempfehlungen zu einem Thema; Ich gab ihm einen Link zu einem Buch , das sich speziell mit diesem Thema befasst. Ich gab auch meine Meinung dazu ab, warum die Herangehensweise an das Problem möglicherweise nicht optimal ist (was meiner Meinung nach viele Leute hier aufgrund von Stimmen und anderen Kommentaren / Antworten zuzustimmen scheinen). Haben Sie Vorschläge zur Verbesserung meiner Antwort?
RarrRarrRarr
6

Ja, du kannst. Die Leute schrieben objektorientiertes C, bevor C ++ oder Objective-C auf den Markt kamen. Sowohl C ++ als auch Objective-C waren teilweise Versuche, einige der in C verwendeten OO-Konzepte als Teil der Sprache zu formalisieren.

Hier ist ein wirklich einfaches Programm, das zeigt, wie Sie etwas erstellen können, das aussieht / ein Methodenaufruf ist (es gibt bessere Möglichkeiten, dies zu tun. Dies ist nur ein Beweis dafür, dass die Sprache die Konzepte unterstützt):

#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;
}
Alan Storm
quelle
6

Natürlich ist es nicht so hübsch wie die Verwendung einer Sprache mit integrierter Unterstützung. Ich habe sogar "objektorientierten Assembler" geschrieben.

Darron
quelle
6

Ein kleiner OOC-Code zum Hinzufügen:

#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;
}
Benutzer922475
quelle
5

Ich grabe das seit einem Jahr:

Da das GObject-System mit reinem C schwer zu verwenden ist, habe ich versucht, einige nette Makros zu schreiben, um den OO-Stil mit C zu vereinfachen.

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

Hier ist meine Projektseite (ich habe nicht genug Zeit, um en. Doc zu schreiben, aber das Dokument auf Chinesisch ist viel besser).

OOC-GCC

Dameng
quelle
Die CLASS STATIC ASM NEW DELETE ST ... sind Makros im OOC-GCC
Dameng
4

Welche Artikel oder Bücher eignen sich für die Verwendung von OOP-Konzepten in C?

Dave Hansons C-Schnittstellen und -Implementierungen eignen sich hervorragend für die Kapselung und Benennung sowie für die Verwendung von Funktionszeigern. Dave versucht nicht, die Vererbung zu simulieren.

Norman Ramsey
quelle
4

OOP ist nur ein Paradigma, bei dem Daten in Programmen wichtiger als Code sind. OOP ist keine Sprache. So wie einfaches C eine einfache Sprache ist, ist auch OOP in einfachem C einfach.

anonyme
quelle
3
Gut gesagt, aber das sollte ein Kommentar sein.
pqsk
4

Möglicherweise möchten Sie sich mit der Implementierung des Xt- Toolkits für X Window befassen . Sicher, es wird lang im Zahn, aber viele der verwendeten Strukturen wurden so entworfen, dass sie im traditionellen C auf OO-Weise funktionieren. Im Allgemeinen bedeutet dies, hier und da eine zusätzliche Indirektionsebene hinzuzufügen und Strukturen zu entwerfen, die übereinander liegen.

Auf diese Weise können Sie wirklich viel in der Art von OO tun, die sich in C befindet, obwohl es sich manchmal so anfühlt, sind OO-Konzepte nicht vollständig aus dem Geist von entstanden #include<favorite_OO_Guru.h>. Sie waren wirklich viele der etablierten Best Practices der Zeit. OO Sprachen und Systeme destillierten und verstärkten nur Teile des Programmierzeitgeists des Tages.

Peter Mortensen
quelle
4

Die Antwort auf die Frage lautet "Ja, Sie können".

Das objektorientierte C (OOC) -Kit ist für diejenigen gedacht, die objektorientiert programmieren möchten, aber auch am guten alten C festhalten. OOC implementiert Klassen, Einzel- und Mehrfachvererbung sowie Ausnahmebehandlung.

Eigenschaften

• Verwendet nur C-Makros und -Funktionen, keine Spracherweiterungen erforderlich! (ANSI-C)

• Einfach zu lesender Quellcode für Ihre Anwendung. Es wurde darauf geachtet, die Dinge so einfach wie möglich zu gestalten.

• Einzelvererbung von Klassen

• Mehrfachvererbung durch Schnittstellen und Mixins (seit Version 1.3)

• Implementieren von Ausnahmen (in reinem C!)

• Virtuelle Funktionen für Klassen

• Externes Tool für die einfache Implementierung von Klassen

Weitere Informationen finden Sie unter http://ooc-coding.sourceforge.net/ .

Sachin Mhetre
quelle
4

Es scheint, als würden Leute versuchen, den C ++ - Stil mit C zu emulieren. Meiner Meinung nach ist objektorientiertes Programmieren C wirklich strukturorientiertes Programmieren. Sie können jedoch Dinge wie späte Bindung, Kapselung und Vererbung erreichen. Für die Vererbung definieren Sie explizit einen Zeiger auf die Basisstrukturen in Ihrer Unterstruktur, und dies ist offensichtlich eine Form der Mehrfachvererbung. Sie müssen auch feststellen, ob Ihre

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

kompilieren mit c_compiler main.c inherited_class_1.obj inherited_class_2.obj private_class.obj.

Der Rat ist also, sich an einen reinen C-Stil zu halten und nicht zu versuchen, einen C ++ - Stil zu erzwingen. Auch auf diese Weise eignet sich eine sehr saubere Methode zum Erstellen einer API.

user2074102
quelle
Für die Vererbung wird normalerweise die Basisklasse oder Instanzstruktur in die abgeleitete eingebettet, nicht separat zugewiesen und mithilfe von Zeigern referenziert. Auf diese Weise befindet sich die oberste Basis immer am Anfang der Strukturen der abgeleiteten Typen, sodass sie problemlos ineinander umgewandelt werden können, was mit Zeigern, die sich möglicherweise in einem Versatz befinden, nicht möglich ist.
underscore_d
2

Unter http://slkpg.byethost7.com/instance.html finden Sie eine weitere Neuerung von OOP in C. Es werden Instanzdaten für die Wiedereintritte nur mit nativem C hervorgehoben. Die Mehrfachvererbung erfolgt manuell mithilfe von Funktionsumbrüchen. Die Typensicherheit bleibt erhalten. Hier ist ein kleines Beispiel:

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;
}
slkpg
quelle
2

Ich bin etwas spät zur Party, aber ich möchte meine Erfahrungen zu diesem Thema teilen: Ich arbeite heutzutage mit eingebetteten Dingen, und der einzige (zuverlässige) Compiler, den ich habe, ist C, sodass ich objektorientiert anwenden möchte Ansatz in meinen eingebetteten Projekten in C.

Die meisten Lösungen, die ich bisher gesehen habe, verwenden Typecasts stark, sodass wir die Typensicherheit verlieren: Der Compiler hilft Ihnen nicht, wenn Sie einen Fehler machen. Dies ist völlig inakzeptabel.

Anforderungen, die ich habe:

  • Vermeiden Sie Typosendungen so weit wie möglich, damit wir die Typensicherheit nicht verlieren.
  • Polymorphismus: Wir sollten in der Lage sein, virtuelle Methoden zu verwenden, und der Benutzer der Klasse sollte nicht wissen, ob eine bestimmte Methode virtuell ist oder nicht.
  • Mehrfachvererbung: Ich verwende es nicht oft, aber manchmal möchte ich wirklich, dass eine Klasse mehrere Schnittstellen implementiert (oder mehrere Superklassen erweitert).

Ich habe meinen Ansatz in diesem Artikel ausführlich erläutert: Objektorientierte Programmierung in C ; Außerdem gibt es ein Dienstprogramm zur automatischen Generierung von Boilerplate-Code für Basis- und abgeleitete Klassen.

Dmitry Frank
quelle
2

Ich habe eine kleine Bibliothek gebaut, in der ich das ausprobiert habe und für mich funktioniert es wirklich gut. Also dachte ich, ich teile die Erfahrung.

https://github.com/thomasfuhringer/oxygen

Die Einzelvererbung kann ganz einfach mithilfe einer Struktur implementiert und für jede andere untergeordnete Klasse erweitert werden. Eine einfache Umwandlung in die übergeordnete Struktur ermöglicht die Verwendung übergeordneter Methoden für alle Nachkommen. Solange Sie wissen, dass eine Variable auf eine Struktur verweist, die diese Art von Objekt enthält, können Sie jederzeit in die Stammklasse umwandeln und eine Selbstbeobachtung durchführen.

Wie bereits erwähnt, sind virtuelle Methoden etwas schwieriger. Aber sie sind machbar. Um die Dinge einfach zu halten, verwende ich einfach eine Reihe von Funktionen in der Klassenbeschreibungsstruktur, die jede untergeordnete Klasse kopiert und bei Bedarf einzelne Slots neu auffüllt.

Die Mehrfachvererbung wäre ziemlich kompliziert zu implementieren und hat erhebliche Auswirkungen auf die Leistung. Also lasse ich es. Ich halte es in einigen Fällen für wünschenswert und nützlich, die realen Lebensumstände sauber zu modellieren, aber in wahrscheinlich 90% der Fälle deckt die Einzelvererbung die Bedürfnisse ab. Und Einzelvererbung ist einfach und kostet nichts.

Auch die Typensicherheit ist mir egal. Ich denke, Sie sollten sich nicht auf den Compiler verlassen, um Programmierfehler zu vermeiden. Und es schützt Sie sowieso nur vor einem kleinen Teil der Fehler.

In einer objektorientierten Umgebung möchten Sie normalerweise auch die Referenzzählung implementieren, um die Speicherverwaltung so weit wie möglich zu automatisieren. Daher habe ich auch einen Referenzzähler in die Stammklasse "Objekt" und einige Funktionen zum Einkapseln der Zuordnung und Freigabe des Heapspeichers eingefügt.

Es ist alles sehr einfach und schlank und gibt mir das Wesentliche von OO, ohne mich zu zwingen, mich mit dem Monster zu befassen, das C ++ ist. Und ich behalte die Flexibilität, in C Land zu bleiben, was unter anderem die Integration von Bibliotheken von Drittanbietern erleichtert.

Thomas F.
quelle
1

Ich schlage vor, Objective-C zu verwenden, eine Obermenge von C.

Während Objective-C 30 Jahre alt ist, können Sie eleganten Code schreiben.

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

SteAp
quelle
In diesem Fall würde ich stattdessen C ++ empfehlen, da es tatsächlich objektorientiert ist ...
yyny
Dies ist keine Antwort. Aber trotzdem, @YoYoYonnY: Ich verwende Objective-C nicht und verwende C ++, aber solche Kommentare nützen nichts ohne Grundlage, und Sie haben keine angegeben. Warum behaupten Sie, dass Objective-C nicht "tatsächlich objektorientiert ..." ist? Und warum ist C ++ dort erfolgreich, wo Objective-C fehlschlägt? Das Lustige ist, dass Objective-C buchstäblich das Wort Object in seinem Namen hat, während C ++ sich als Multi-Paradigmen-Sprache vermarktet, nicht als OOP-Sprache (dh nicht primär OOP, und nach Ansicht einiger eher extremer Leute nicht OOP überhaupt) ... bist du dir also sicher, dass du diese Namen nicht falsch herum verstanden hast?
underscore_d
0

Ja, aber ich habe noch nie jemanden gesehen, der versucht hat, mit C. irgendeine Art von Polymorphismus zu implementieren.

Paul Morel
quelle
6
Sie müssen sich mehr umschauen :) Zum Beispiel verfügt Microsoft Direct X über eine polymorphe C-Oberfläche.
AShelly
8
Schauen Sie sich zum Beispiel die Implementierung des Linux-Kernels an. Es ist eine sehr verbreitete und weit verbreitete Praxis in C.
Ilya
3
Auch glib ist polymorph oder kann so verwendet werden, dass Polymorphismus möglich ist (es ist wie in C ++, man muss explizit sagen, welche Aufrufe virtuell sind)
Spudd86
1
Polymorphismus ist in C nicht so selten, andererseits ist Mehrfachvererbung.
Johan Bjäreholt