Angenommen, ich muss C verwenden (kein C++ oder objektorientierte Compiler) und ich habe keine dynamische Speicherzuweisung, was sind einige Techniken, die ich verwenden kann, um eine Klasse zu implementieren, oder eine gute Annäherung an eine Klasse? Ist es immer eine gute Idee, die “Klasse” in einer separaten Datei zu isolieren? Angenommen, wir können den Speicher vorab zuweisen, indem wir eine feste Anzahl von Instanzen annehmen oder sogar die Referenz auf jedes Objekt vor der Kompilierzeit als Konstante definieren. Fühlen Sie sich frei, Vermutungen darüber anzustellen, welches OOP-Konzept ich implementieren muss (es wird variieren) und die beste Methode für jedes vorschlagen.
Beschränkungen:
Ich muss C und kein OOP verwenden, weil ich Code für ein eingebettetes System schreibe und der Compiler und die bereits vorhandene Codebasis in C sind.
Es gibt keine dynamische Speicherzuweisung, weil wir nicht genug Speicher haben, um davon auszugehen, dass er uns nicht ausgeht, wenn wir mit der dynamischen Zuweisung beginnen.
Die Compiler, mit denen wir arbeiten, haben keine Probleme mit Funktionszeigern
Pflichtfrage: Muss man objektorientierten Code schreiben? Wenn Sie es aus irgendeinem Grund tun, ist das in Ordnung, aber Sie werden einen ziemlich harten Kampf führen. Es ist wahrscheinlich das Beste, wenn Sie vermeiden, objektorientierten Code in C zu schreiben. Es ist sicherlich möglich – siehe die ausgezeichnete Antwort von Unwind -, aber es ist nicht gerade “einfach”, und wenn Sie an einem eingebetteten System mit begrenztem Speicher arbeiten es möglicherweise nicht machbar. Ich kann mich jedoch irren – ich versuche nicht, Sie davon abzubringen, sondern präsentiere nur einige Kontrapunkte, die möglicherweise nicht präsentiert wurden.
– Chris Lutz
10. September 2009 um 8:04 Uhr
Genau genommen müssen wir das nicht. Die Komplexität des Systems hat den Code jedoch nicht wartbar gemacht. Mein Gefühl ist, dass der beste Weg, die Komplexität zu reduzieren, darin besteht, einige OOP-Konzepte zu implementieren. Vielen Dank für alle, die innerhalb von 3 Minuten antworten. Ihr seid verrückt und schnell!
– Ben Gartner
10. September 2009 um 8:09 Uhr
Dies ist nur meine bescheidene Meinung, aber OOP macht Code nicht sofort wartbar. Es kann die Verwaltung vereinfachen, ist aber nicht unbedingt wartungsfreundlicher. Sie können “Namespaces” in C haben (die Apache Portable Runtime stellt allen globalen Symbolen das Präfix voran apr_ und GLib stellt ihnen das Präfix voran g_ um einen Namensraum zu erstellen) und andere Organisationsfaktoren ohne OOP. Wenn Sie die App sowieso umstrukturieren, würde ich in Betracht ziehen, einige Zeit damit zu verbringen, eine besser wartbare Verfahrensstruktur zu finden.
– Chris Lutz
10. September 2009 um 8:14 Uhr
Dies wurde schon endlos diskutiert – haben Sie sich eine der vorherigen Antworten angesehen?
– Larry Watanabe
12. September 2009 um 23:09 Uhr
Diese Quelle, die in einer gelöschten Antwort von mir enthalten war, kann auch hilfreich sein: planetpdf.com/codecuts/pdfs/ooc.pdf Es beschreibt einen vollständigen Ansatz für OO in C.
– Ruben Steins
15. August 2019 um 9:32 Uhr
entspannen
Das hängt von dem genauen “objektorientierten” Feature-Set ab, das Sie haben möchten. Wenn Sie Dinge wie Überladen und/oder virtuelle Methoden benötigen, müssen Sie wahrscheinlich Funktionszeiger in Strukturen einschließen:
Dazu müssen Sie natürlich auch einen Konstruktor implementieren, der dafür sorgt, dass der Funktionszeiger richtig eingerichtet ist. Normalerweise würden Sie der Instanz Speicher dynamisch zuweisen, aber Sie können dies auch dem Aufrufer überlassen:
Wenn Sie mehrere verschiedene Konstruktoren möchten, müssen Sie die Funktionsnamen “dekorieren”, Sie können nicht mehr als einen haben rectangle_new() Funktion:
Hier ist ein einfaches Beispiel, das die Verwendung zeigt:
int main(void)
{
RectangleClass r1;
rectangle_new_with_lengths(&r1, 4.f, 5.f);
printf("rectangle r1's area is %f units square\n", shape_computeArea(&r1));
return 0;
}
Ich hoffe, das gibt dir zumindest ein paar Ideen. Für ein erfolgreiches und reichhaltiges objektorientiertes Framework in C schauen Sie sich Glibs an GObject Bibliothek.
Beachten Sie auch, dass oben keine explizite “Klasse” modelliert wird, jedes Objekt hat seine eigenen Methodenzeiger, was etwas flexibler ist, als Sie es normalerweise in C++ finden würden. Außerdem kostet es Speicherplatz. Sie könnten dem entkommen, indem Sie die Methodenzeiger in a stopfen class Struktur und erfinde einen Weg für jede Objektinstanz, auf eine Klasse zu verweisen.
Da man nicht versuchen musste, objektorientiertes C zu schreiben, ist es normalerweise am besten, Funktionen zu erstellen, die übernehmen const ShapeClass * oder const void * als Argumente? Es scheint, dass letzteres bei der Vererbung etwas netter ist, aber ich kann Argumente in beide Richtungen sehen …
– Chris Lutz
10. September 2009 um 8:00 Uhr
@Chris: Ja, das ist eine schwierige Frage. 😐 GTK+ (das GObject verwendet) verwendet die richtige Klasse, dh RectangleClass *. Das bedeutet, dass Sie oft Umwandlungen vornehmen müssen, aber sie bieten praktische Makros, die Ihnen dabei helfen, sodass Sie immer BASECLASS *p in SUBCLASS * umwandeln können, indem Sie nur SUBCLASS(p) verwenden.
– abschalten
10. September 2009 um 8:03 Uhr
Mein Compiler schlägt in der zweiten Codezeile fehl: float (*computeArea)(const ShapeClass *shape); sagt, dass ShapeClass ist ein unbekannter Typ.
– DanielSank
9. August 2016 um 9:01 Uhr
@DanielSank, das liegt an der fehlenden Vorwärtsdeklaration, die von der ‘typedef struct’ benötigt wird (im angegebenen Beispiel nicht gezeigt). Weil die struct sich selbst referenziert, erfordert es a Erklärung vor es ist definiert. Dies wird hier in Lundins Antwort anhand eines Beispiels erläutert. Das Ändern des Beispiels, um die Vorwärtsdeklaration einzuschließen, sollte Ihr Problem lösen; typedef struct ShapeClass ShapeClass; struct ShapeClass { float (*computeArea)(const ShapeClass *shape); };
– S. Whittaker
31. Oktober 2017 um 9:15 Uhr
Was passiert, wenn Rectangle eine Funktion hat, die nicht alle Shapes haben. Zum Beispiel get_corners(). Ein Kreis würde dies nicht implementieren, aber ein Rechteck könnte dies tun. Wie greifen Sie auf eine Funktion zu, die nicht Teil der übergeordneten Klasse ist, von der Sie geerbt haben?
– Otus
7. September 2018 um 17:04 Uhr
erelender
Ich musste es auch einmal für eine Hausaufgabe machen. Ich bin diesem Ansatz gefolgt:
Definieren Sie Ihre Datenelemente in einer Struktur.
Definieren Sie Ihre Funktionsmitglieder, die als erstes Argument einen Zeiger auf Ihre Struktur annehmen.
Tun Sie dies in einem Header und einem c. Header für Strukturdefinitionen und Funktionsdeklarationen, c für Implementierungen.
Ein einfaches Beispiel wäre dieses:
/// Queue.h
struct Queue
{
/// members
}
typedef struct Queue Queue;
void push(Queue* q, int element);
void pop(Queue* q);
// etc.
///
Dies habe ich in der Vergangenheit getan, aber mit dem Hinzufügen von Fälschungsumfang, indem Funktionsprototypen nach Bedarf entweder in der .c- oder .h-Datei platziert werden (wie ich in meiner Antwort erwähnt habe).
– Taylor Leese
10. September 2009 um 8:01 Uhr
Ich mag das, die Struct-Deklaration weist den gesamten Speicher zu. Aus irgendeinem Grund habe ich vergessen, dass dies gut funktionieren würde.
– Ben Gartner
10. September 2009 um 8:12 Uhr
Ich denke, du brauchst eine typedef struct Queue Queue; da drin.
– Craig McQueen
11. September 2009 um 0:05 Uhr
Oder einfach typedef struct { /* members */ } Queue;
– Brooks Moses
11. September 2009 um 6:20 Uhr
#Craig: Danke für die Erinnerung, aktualisiert.
– erelender
11. September 2009 um 7:41 Uhr
Peter Kirkham
Wenn Sie nur eine Klasse möchten, verwenden Sie ein Array von structs als die „Objekte“-Daten und übergeben Zeiger darauf an die „Member“-Funktionen. Sie können verwenden typedef struct _whatever Whatever vor der Deklaration struct _whatever um die Implementierung vor dem Client-Code zu verbergen. Es gibt keinen Unterschied zwischen einem solchen “Objekt” und der C-Standardbibliothek FILE Objekt.
Wenn Sie mehr als eine Klasse mit Vererbung und virtuellen Funktionen wünschen, ist es üblich, Zeiger auf die Funktionen als Mitglieder der Struktur oder einen gemeinsam genutzten Zeiger auf eine Tabelle mit virtuellen Funktionen zu haben. Das GObject Die Bibliothek verwendet sowohl diesen als auch den Typedef-Trick und ist weit verbreitet.
Sehr interessiert. Kennt sich jemand mit der Zulassung aus? Für meine Zwecke bei der Arbeit wird das Ablegen einer Open-Source-Bibliothek in ein Projekt aus rechtlicher Sicht wahrscheinlich nicht funktionieren.
– Ben Gartner
10. September 2009 um 8:20 Uhr
GTK+ und alle Bibliotheken, die Teil dieses Projekts sind (einschließlich GObject), sind unter der GNU LGPL lizenziert, was bedeutet, dass Sie von proprietärer Software aus darauf verlinken können. Ich weiß jedoch nicht, ob das für eingebettete Arbeiten machbar sein wird.
– Chris Lutz
10. September 2009 um 8:31 Uhr
yyny
Ich werde ein einfaches Beispiel dafür geben, wie OOP in C durchgeführt werden sollte. Mir ist klar, dass dieser Thread aus dem Jahr 2009 stammt, aber ich möchte ihn trotzdem hinzufügen.
/// Object.h
typedef struct Object {
uuid_t uuid;
} Object;
int Object_init(Object *self);
uuid_t Object_get_uuid(Object *self);
int Object_clean(Object *self);
/// Person.h
typedef struct Person {
Object obj;
char *name;
} Person;
int Person_init(Person *self, char *name);
int Person_greet(Person *self);
int Person_clean(Person *self);
/// Object.c
#include "object.h"
int Object_init(Object *self)
{
self->uuid = uuid_new();
return 0;
}
uuid_t Object_get_uuid(Object *self)
{ // Don't actually create getters in C...
return self->uuid;
}
int Object_clean(Object *self)
{
uuid_free(self->uuid);
return 0;
}
/// Person.c
#include "person.h"
int Person_init(Person *self, char *name)
{
Object_init(&self->obj); // Or just Object_init(&self);
self->name = strdup(name);
return 0;
}
int Person_greet(Person *self)
{
printf("Hello, %s", self->name);
return 0;
}
int Person_clean(Person *self)
{
free(self->name);
Object_clean(self);
return 0;
}
/// main.c
int main(void)
{
Person p;
Person_init(&p, "John");
Person_greet(&p);
Object_get_uuid(&p); // Inherited function
Person_clean(&p);
return 0;
}
Das grundlegende Konzept besteht darin, die „geerbte Klasse“ an der Spitze 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 Optimierungen). Wenn nun der Zeiger der Struktur auf die „geerbte Klasse“ umgewandelt wird, kann die „geerbte Klasse“ auf die „geerbten Werte“ auf die gleiche Weise zugreifen, wie sie normalerweise auf ihre Mitglieder zugreifen würde.
Dies und einige Namenskonventionen für Konstruktoren, Destruktoren, Zuweisungs- und Freigabefunktionen (ich empfehle init, clean, new, free) werden Sie weit bringen.
Verwenden Sie wie bei virtuellen Funktionen Funktionszeiger in der Struktur, möglicherweise mit Class_func(…); Verpackung auch. Fügen Sie für (einfache) Vorlagen einen size_t-Parameter hinzu, um die Größe zu bestimmen, verlangen Sie einen void*-Zeiger oder verlangen Sie einen Klassentyp mit genau der Funktionalität, die Ihnen wichtig ist. (z. B. int GetUUID(Object *self); GetUUID(&p);)
Sehr interessiert. Kennt sich jemand mit der Zulassung aus? Für meine Zwecke bei der Arbeit wird das Ablegen einer Open-Source-Bibliothek in ein Projekt aus rechtlicher Sicht wahrscheinlich nicht funktionieren.
– Ben Gartner
10. September 2009 um 8:20 Uhr
GTK+ und alle Bibliotheken, die Teil dieses Projekts sind (einschließlich GObject), sind unter der GNU LGPL lizenziert, was bedeutet, dass Sie von proprietärer Software aus darauf verlinken können. Ich weiß jedoch nicht, ob das für eingebettete Arbeiten machbar sein wird.
– Chris Lutz
10. September 2009 um 8:31 Uhr
Taylor Leese
Verwenden ein struct um die Datenmember einer Klasse zu simulieren. In Bezug auf den Methodenumfang können Sie private Methoden simulieren, indem Sie die Privatgelände Funktionsprototypen in der .c-Datei und der Öffentlichkeit Funktionen in der .h-Datei.
14248600cookie-checkWie implementiert man eine Klasse in C? [closed]yes
Pflichtfrage: Muss man objektorientierten Code schreiben? Wenn Sie es aus irgendeinem Grund tun, ist das in Ordnung, aber Sie werden einen ziemlich harten Kampf führen. Es ist wahrscheinlich das Beste, wenn Sie vermeiden, objektorientierten Code in C zu schreiben. Es ist sicherlich möglich – siehe die ausgezeichnete Antwort von Unwind -, aber es ist nicht gerade “einfach”, und wenn Sie an einem eingebetteten System mit begrenztem Speicher arbeiten es möglicherweise nicht machbar. Ich kann mich jedoch irren – ich versuche nicht, Sie davon abzubringen, sondern präsentiere nur einige Kontrapunkte, die möglicherweise nicht präsentiert wurden.
– Chris Lutz
10. September 2009 um 8:04 Uhr
Genau genommen müssen wir das nicht. Die Komplexität des Systems hat den Code jedoch nicht wartbar gemacht. Mein Gefühl ist, dass der beste Weg, die Komplexität zu reduzieren, darin besteht, einige OOP-Konzepte zu implementieren. Vielen Dank für alle, die innerhalb von 3 Minuten antworten. Ihr seid verrückt und schnell!
– Ben Gartner
10. September 2009 um 8:09 Uhr
Dies ist nur meine bescheidene Meinung, aber OOP macht Code nicht sofort wartbar. Es kann die Verwaltung vereinfachen, ist aber nicht unbedingt wartungsfreundlicher. Sie können “Namespaces” in C haben (die Apache Portable Runtime stellt allen globalen Symbolen das Präfix voran
apr_
und GLib stellt ihnen das Präfix vorang_
um einen Namensraum zu erstellen) und andere Organisationsfaktoren ohne OOP. Wenn Sie die App sowieso umstrukturieren, würde ich in Betracht ziehen, einige Zeit damit zu verbringen, eine besser wartbare Verfahrensstruktur zu finden.– Chris Lutz
10. September 2009 um 8:14 Uhr
Dies wurde schon endlos diskutiert – haben Sie sich eine der vorherigen Antworten angesehen?
– Larry Watanabe
12. September 2009 um 23:09 Uhr
Diese Quelle, die in einer gelöschten Antwort von mir enthalten war, kann auch hilfreich sein: planetpdf.com/codecuts/pdfs/ooc.pdf Es beschreibt einen vollständigen Ansatz für OO in C.
– Ruben Steins
15. August 2019 um 9:32 Uhr