Wie implementiert man eine Klasse in C? [closed]

Lesezeit: 11 Minuten

Benutzeravatar von Ben Gartner
Ben Gartner

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


Benutzeravatar von unwind
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:

typedef struct {
  float (*computeArea)(const ShapeClass *shape);
} ShapeClass;

float shape_computeArea(const ShapeClass *shape)
{
  return shape->computeArea(shape);
}

Auf diese Weise können Sie eine Klasse implementieren, indem Sie die Basisklasse “erben” und eine geeignete Funktion implementieren:

typedef struct {
  ShapeClass shape;
  float width, height;
} RectangleClass;

static float rectangle_computeArea(const ShapeClass *shape)
{
  const RectangleClass *rect = (const RectangleClass *) shape;
  return rect->width * rect->height;
}

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:

void rectangle_new(RectangleClass *rect)
{
  rect->width = rect->height = 0.f;
  rect->shape.computeArea = rectangle_computeArea;
}

Wenn Sie mehrere verschiedene Konstruktoren möchten, müssen Sie die Funktionsnamen “dekorieren”, Sie können nicht mehr als einen haben rectangle_new() Funktion:

void rectangle_new_with_lengths(RectangleClass *rect, float width, float height)
{
  rectangle_new(rect);
  rect->width = width;
  rect->height = height;
}

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

Benutzeravatar von erelender
erelender

Ich musste es auch einmal für eine Hausaufgabe machen. Ich bin diesem Ansatz gefolgt:

  1. Definieren Sie Ihre Datenelemente in einer Struktur.
  2. Definieren Sie Ihre Funktionsmitglieder, die als erstes Argument einen Zeiger auf Ihre Struktur annehmen.
  3. 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

Benutzeravatar von Pete Kirkham
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.

Es gibt auch ein Buch über Techniken dafür, das online verfügbar ist – Objektorientierte Programmierung mit ANSI C.

  • Kühl! Irgendwelche anderen Empfehlungen für Bücher über OOP in C? Oder andere moderne Designtechniken in C? (oder eingebettete Systeme?)

    – Ben Gartner

    10. September 2009 um 8:22 Uhr

Benutzeravatar von Mark Harrison
Markus Harrison

C Schnittstellen und Implementierungen: Techniken zum Erstellen wiederverwendbarer Software, David R. Hanson

http://www.informit.com/store/product.aspx?isbn=0201498413

Dieses Buch erfüllt Ihre Frage hervorragend. Es ist in der Addison Wesley Professional Computing-Serie.

Das grundlegende Paradigma ist in etwa so:

/* for data structure foo */

FOO *myfoo;
myfoo = foo_create(...);
foo_something(myfoo, ...);
myfoo = foo_append(myfoo, ...);
foo_delete(myfoo);

Sie können sich GOBject ansehen. Es ist eine Betriebssystembibliothek, die Ihnen eine ausführliche Möglichkeit bietet, ein Objekt zu bearbeiten.

http://library.gnome.org/devel/gobject/stable/

  • 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

Benutzeravatar von yyny
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

Benutzeravatar von Taylor Leese
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.

1424860cookie-checkWie implementiert man eine Klasse in C? [closed]

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy