Angenommen, ich muss C verwenden (keine C ++ - oder objektorientierten Compiler) und ich habe keine dynamische Speicherzuordnung. Welche Techniken kann ich verwenden, um eine Klasse zu implementieren, oder eine gute Annäherung an eine Klasse? Ist es immer eine gute Idee, die "Klasse" in eine separate Datei zu isolieren? Angenommen, wir können den Speicher vorab zuweisen, indem wir eine feste Anzahl von Instanzen annehmen oder sogar den Verweis auf jedes Objekt als Konstante vor der Kompilierungszeit definieren. Sie können gerne Annahmen darüber treffen, welches OOP-Konzept ich implementieren muss (es wird variieren) und die jeweils beste Methode vorschlagen.
Beschränkungen:
- Ich muss C und kein OOP verwenden, da ich Code für ein eingebettetes System schreibe und der Compiler und die bereits vorhandene Codebasis in C sind.
- Es gibt keine dynamische Speicherzuweisung, da wir nicht über genügend Speicher verfügen, um davon auszugehen, dass wir nicht ausgehen, wenn wir mit der dynamischen Zuweisung beginnen.
- Die Compiler, mit denen wir arbeiten, haben keine Probleme mit Funktionszeigern
apr_
und GLib ihnen Präfixe vorg_
, um einen Namespace zu erstellen) und andere Organisationsfaktoren ohne OOP verwenden. Wenn Sie die App trotzdem umstrukturieren möchten, würde ich in Betracht ziehen, einige Zeit damit zu verbringen, eine wartbarere Verfahrensstruktur zu entwickeln.Antworten:
Dies hängt von dem genauen "objektorientierten" Funktionsumfang ab, den Sie haben möchten. Wenn Sie Dinge wie Überladung und / oder virtuelle Methoden benötigen, müssen Sie wahrscheinlich Funktionszeiger in Strukturen einfügen:
Auf diese Weise können Sie eine Klasse implementieren, indem Sie die Basisklasse "erben" und eine geeignete Funktion implementieren:
Dazu müssen Sie natürlich auch einen Konstruktor implementieren, der sicherstellt, dass der Funktionszeiger richtig eingerichtet ist. Normalerweise würden Sie der Instanz dynamisch Speicher zuweisen, aber Sie können den Aufrufer dies auch tun lassen:
Wenn Sie mehrere verschiedene Konstruktoren möchten, müssen Sie die Funktionsnamen "dekorieren". Sie können nicht mehr als eine
rectangle_new()
Funktion haben:Hier ist ein grundlegendes Beispiel für die Verwendung:
Ich hoffe, das gibt Ihnen zumindest einige Ideen. Ein erfolgreiches und umfangreiches objektorientiertes Framework in C finden Sie in der GObject- Bibliothek von glib .
Beachten Sie auch, dass oben keine explizite "Klasse" modelliert wird. Jedes Objekt verfügt über eigene Methodenzeiger, die etwas flexibler sind als in C ++ üblich. Außerdem kostet es Speicher. Sie könnten davon abweichen, indem Sie die Methodenzeiger in eine
class
Struktur einfügen und eine Möglichkeit für jede Objektinstanz erfinden, auf eine Klasse zu verweisen.quelle
const ShapeClass *
oderconst void *
als Argumente dienen , da Sie nicht versuchen mussten, objektorientiertes C zu schreiben ? Es scheint, dass Letzteres bei der Vererbung etwas besser ist, aber ich kann Argumente in beide Richtungen sehen ...float (*computeArea)(const ShapeClass *shape);
DasShapeClass
ist ein unbekannter Typ.struct
Referenzen selbst eine Deklaration erfordern, bevor sie definiert werden. Dies wird hier in Lundins Antwort anhand eines Beispiels erläutert . Wenn Sie das Beispiel so ändern, dass es die Weiterleitungsdeklaration enthält, sollte Ihr Problem behoben sein.typedef struct ShapeClass ShapeClass; struct ShapeClass { float (*computeArea)(const ShapeClass *shape); };
Ich musste es auch einmal für eine Hausaufgabe machen. Ich folgte diesem Ansatz:
Ein einfaches Beispiel wäre dies:
quelle
typedef struct Queue Queue;
da drin einen .Wenn Sie nur eine Klasse möchten, verwenden Sie ein Array von
struct
s als "Objekt" -Daten und übergeben Sie Zeiger auf diese an die "Member" -Funktionen. Sie könnentypedef struct _whatever Whatever
vor dem Deklarieren verwendenstruct _whatever
, um die Implementierung vor dem Clientcode auszublenden. Es gibt keinen Unterschied zwischen einem solchen "Objekt" und dem C-StandardbibliotheksobjektFILE
.Wenn Sie mehr als eine Klasse mit Vererbung und virtuellen Funktionen möchten, haben Sie häufig Zeiger auf die Funktionen als Mitglieder der Struktur oder einen gemeinsamen Zeiger auf eine Tabelle mit virtuellen Funktionen. Die GObject- Bibliothek verwendet sowohl diesen als auch den typedef-Trick und ist weit verbreitet.
Es gibt auch ein Buch über Techniken für diese online verfügbar - Objektorientierte Programmierung mit ANSI C .
quelle
Sie können sich GOBject ansehen. Es ist eine Betriebssystembibliothek, die Ihnen eine ausführliche Möglichkeit bietet, ein Objekt zu erstellen.
http://library.gnome.org/devel/gobject/stable/
quelle
C Schnittstellen und Implementierungen: Techniken zum Erstellen wiederverwendbarer Software , David R. Hanson
Dieses Buch behandelt Ihre Frage hervorragend. Es ist in der Addison Wesley Professional Computing-Reihe.
Das grundlegende Paradigma ist ungefähr so:
quelle
Ich werde ein einfaches Beispiel dafür geben, wie OOP in C durchgeführt werden sollte. Mir ist klar, dass dieser Thead aus dem Jahr 2009 stammt, möchte dies aber trotzdem hinzufügen.
Das Grundkonzept besteht darin, die 'geerbte Klasse' oben in der Struktur zu platzieren. Auf diese Weise greift der Zugriff auf die ersten 4 Bytes in der Struktur auch auf die ersten 4 Bytes in der 'geerbten Klasse' zu (unter der Annahme nicht verrückter Optimalisierungen). Wenn nun der Zeiger der Struktur auf die 'geerbte Klasse' umgewandelt wird, kann die 'geerbte Klasse' auf die 'geerbten Werte' genauso zugreifen, wie sie normalerweise auf ihre Mitglieder zugreifen würde.
Diese und einige Namenskonventionen für Konstruktoren, Destruktoren, Zuordnungs- und Deallocarion-Funktionen (ich empfehle init, clean, new, free) bringen Sie weit.
Verwenden Sie für virtuelle Funktionen Funktionszeiger in der Struktur, möglicherweise mit Class_func (...). Wrapper auch. Fügen Sie für (einfache) Vorlagen einen size_t-Parameter hinzu, um die Größe zu bestimmen, benötigen Sie einen void * -Zeiger oder einen 'class'-Typ mit genau der Funktionalität, die Sie interessiert. (zB int GetUUID (Object * self); GetUUID (& p);)
quelle
Verwenden Sie a
struct
, um die Datenelemente einer Klasse zu simulieren. In Bezug auf den Methodenumfang können Sie private Methoden simulieren, indem Sie die Prototypen für private Funktionen in die .c-Datei und die öffentlichen Funktionen in die .h-Datei einfügen.quelle
quelle
In Ihrem Fall könnte die gute Annäherung der Klasse eine ADT sein . Aber es wird immer noch nicht dasselbe sein.
quelle
Meine Strategie ist:
Hat jemand Probleme, Lücken, potenzielle Fallstricke oder versteckte Vor- und Nachteile bei beiden Variationen dieses Ansatzes? Wenn ich eine Entwurfsmethode neu erfinde (und ich nehme an, dass ich es sein muss), können Sie mich auf den Namen verweisen?
quelle
Siehe auch diese Antwort und diese
Es ist möglich. Es scheint immer eine gute Idee zu sein, aber danach wird es zu einem Alptraum für die Wartung. Ihr Code ist übersät mit Codeteilen, die alles zusammenbinden. Ein neuer Programmierer hat viele Probleme beim Lesen und Verstehen des Codes, wenn Sie Funktionszeiger verwenden, da nicht klar ist, welche Funktionen aufgerufen werden.
Das Ausblenden von Daten mit get / set-Funktionen ist in C einfach zu implementieren, hört aber dort auf. Ich habe mehrere Versuche in der eingebetteten Umgebung gesehen und am Ende ist es immer ein Wartungsproblem.
Da Sie alle bereit sind, Wartungsprobleme zu haben, würde ich klar steuern.
quelle
Mein Ansatz wäre es, die
struct
und alle primär zugeordneten Funktionen in eine separate Quelldatei (en) zu verschieben, damit sie "portabel" verwendet werden können.Abhängig von Ihrem Compiler können Sie möglicherweise Funktionen in die aufnehmen
struct
, aber das ist eine sehr compilerspezifische Erweiterung und hat nichts mit der letzten Version des Standards zu tun, den ich routinemäßig verwendet habe :)quelle
Der erste C ++ - Compiler war tatsächlich ein Präprozessor, der den C ++ - Code in C übersetzte.
Es ist also sehr gut möglich, Klassen in C zu haben. Sie könnten versuchen, einen alten C ++ - Präprozessor auszugraben und zu sehen, welche Art von Lösungen er erstellt.
quelle
cfront
; Beim Hinzufügen von Ausnahmen zu C ++ traten Probleme auf - die Behandlung von Ausnahmen ist nicht trivial.GTK basiert vollständig auf C und verwendet viele OOP-Konzepte. Ich habe den Quellcode von GTK gelesen und er ist ziemlich beeindruckend und definitiv einfacher zu lesen. Das Grundkonzept ist, dass jede "Klasse" einfach eine Struktur und zugehörige statische Funktionen ist. Die statischen Funktionen akzeptieren alle die "Instanz" -Struktur als Parameter, tun alles, was dann benötigt wird, und geben bei Bedarf Ergebnisse zurück. Beispielsweise können Sie eine Funktion "GetPosition (CircleStruct obj)" haben. Die Funktion würde einfach durch die Struktur graben, die Positionsnummern extrahieren, wahrscheinlich ein neues PositionStruct-Objekt erstellen, das x und y in das neue PositionStruct stecken und es zurückgeben. GTK implementiert die Vererbung sogar auf diese Weise, indem Strukturen in Strukturen eingebettet werden. ziemlich clever.
quelle
Möchten Sie virtuelle Methoden?
Wenn nicht, definieren Sie einfach eine Reihe von Funktionszeigern in der Struktur selbst. Wenn Sie alle Funktionszeiger Standard-C-Funktionen zuweisen, können Sie Funktionen von C in einer sehr ähnlichen Syntax wie unter C ++ aufrufen.
Wenn Sie virtuelle Methoden haben möchten, wird es komplizierter. Grundsätzlich müssen Sie jeder Struktur eine eigene VTable implementieren und der VTable Funktionszeiger zuweisen, je nachdem, welche Funktion aufgerufen wird. Sie benötigen dann eine Reihe von Funktionszeigern in der Struktur selbst, die wiederum den Funktionszeiger in der VTable aufrufen. Dies ist im Wesentlichen das, was C ++ tut.
TBH aber ... wenn Sie Letzteres wollen, ist es wahrscheinlich besser, nur einen C ++ - Compiler zu finden, den Sie verwenden können, und das Projekt neu zu kompilieren. Ich habe nie verstanden, dass C ++ in Embedded nicht verwendbar ist. Ich habe es schon oft benutzt und es funktioniert schnell und hat keine Speicherprobleme. Sicher, Sie müssen etwas vorsichtiger sein, was Sie tun, aber es ist wirklich nicht so kompliziert.
quelle
C ist keine OOP-Sprache, wie Sie zu Recht betonen. Es gibt also keine integrierte Möglichkeit, eine echte Klasse zu schreiben. Am besten schauen Sie sich Strukturen und Funktionszeiger an . Mit diesen können Sie eine Annäherung an eine Klasse erstellen. Da C jedoch prozedural ist, sollten Sie in Betracht ziehen, mehr C-ähnlichen Code zu schreiben (dh ohne zu versuchen, Klassen zu verwenden).
Wenn Sie C verwenden können, können Sie wahrscheinlich auch C ++ verwenden und Klassen abrufen.
quelle