C: Emulation von Klassen

Es gibt 29 Antworten in diesem Thema, welches 3.320 mal aufgerufen wurde. Der letzte Beitrag (18. Dezember 2023 um 15:19) ist von Unseen.

  • Hi,

    hab' was über C entdeckt, das ich sehr interessant finde, weil es mir ggf. das Leben damit erheblich erleichtern kann: Offenbar ist es möglich, mit Hilfe von structs und Funktionen sowas wie Klassen zu emulieren.

    Das Konzept von Klassen hab' ich unter Python gelernt, dazu verwende ich immer das folgende kleine Programm:

    Der Witz ist nun, daß man das relativ ähnlich auch in C schreiben kann. Hab' Bitte melde dich an, um diesen Link zu sehen. ein Beispiel gepostet.

    Der Code kompiliert/läuft sowohl mit gcc/Linux, als auch mit z88dk/Spectrum, CC65/"Commander X16", CC65/C64.

    Ich denke, das könnte endlich Vieles erheblich einfacher machen, was C angeht, und finde das daher ehrlich gesagt ziemlich aufregend.

  • Du verwendest in dem verlinkten Demo self. Das gibt es doch in C gar nicht, sondern nur in C++. Oder hat sich das in den letzten 20 Jahren geändert?

  • Du kannst in gewöhnlichem C auch mit "Klassen" arbeiten.

    Natürlich hat C Einschränkungen zu C++, aber man kann ganz gut tricksen:

    • am einfachsten ist es, wenn du für jede "Klasse" ein eigenes .c File machst das wie deine Klasse heißt
    • dazu ein eigenes .h File das wie deine Klasse heißt
    • alles was public (sichtbar nach außen) ist, das beginnt mit denselben Buchstaben
    • alle Daten des Objekt sind in einer Struktur (struct)
    • alle Funktionen haben als erstes Argument einen Zeiger auf diese Klassen Struktur
    • die Funktion MyClassNew() hat einen Zeiger auf einen Zeiger auf das Objekt
    • jede Klasse hat diese MyClassNew() und MyClassTerminate() Funktion
    • in dem Klassen .h File ist nur die Definition der Klassen struct sowie die Prototypen der public Funktionen

    Bitte melde dich an, um diesen Link zu sehen. --- Bitte melde dich an, um diesen Link zu sehen. --- Bitte melde dich an, um diesen Link zu sehen.

    • alle Funktionen haben als erstes Argument einen Zeiger auf diese Klassen Struktur

    Das ist im wesentlichen der Clou, eben daß bei Methoden einer Klasse "versteckt" immer ein Zeiger auf das Objekt als Argument definiert wird und "mitkommt".

    Ansonsten sind nun mal die ersten C++-Compiler tatsächlich "nur" C-Compiler mit vorgeschaltetem Bitte melde dich an, um diesen Link zu sehen.gewesen, welcher dann den C++-Quelltext in C-Quelltext transformiert hat. Daraus folgt direkt, daß man auch in C objektorientiert programmieren kann ... wenn man bereit ist, die ganzen Regeln "händisch" einzuhalten, die cfront und C++ automatisch erzwingen.

  • Du verwendest in dem verlinkten Demo self. Das gibt es doch in C gar nicht, sondern nur in C++. Oder hat sich das in den letzten 20 Jahren geändert?

    Naja, ich hab's halt mit diesem Namen definiert. Weil mir auffiel, daß es ziemlich genau das macht, was in Python oder Perl (die kenne ich besser als C++) das "self" auch macht. So kann man statt in Python "self.name" eben schreiben "self->name".

    In dem C-Beispiel ist es genaugenommen ein Zeiger auf das struct, das die "Attribute" hält (in Perl ist es eine Referenz auf einen anonymen Hash, der ebenfalls die Attribute hält). Aber das, was das "self" eigentlich ist, braucht einen ja nicht mehr zu kümmern.

    Man schreibt nur einfach "self->name", wenn man den Wert des Attributs haben will. Das ist der Witz an OOP.

    Geht wie gezeigt in C sogar mit ganzen Strings. Die sind in C sonst ja immer ein Problem.

  • In C++ ist das eigentlich sehr identisch. Self ist hier der 'this' Pointer. Also ein Zeiger auf das aktuelle Objekt. Der Unterschied zwischen

    <object>.<function>() und <object-pointer>-><function>() ist halt, dass -> eine Abkürzung ist für

    *<object-pointer>.<function>()

    *<pointer> holt das Objekt, auf welches der Pointer zeigt, und danach kannst Du die Funktion aufrufen.

    Also:

    KlassenTyp meinObjekt; // Erzeuge ein Objekt vom Typ 'KlassenTyp'

    KlassenTyp * meinObjektPointer; // Erzeuge einen Pointer auf ein Objekt des Typs 'KlassenTyp'

    meinObjektPointer = & meinObjekt; // Hole die Adresse des Objekts 'meinObjekt'.

    int ergebnis = meinObjekt.rechneWas(); // Rufe eine Funktion von 'meinObjekt' auf

    int ergebnis = meinObjektPointer->rechneWas(); // Rufe eine Funktion über den Pointer auf 'meinObjekt' auf

  • Man kann das alles machen, aber wozu? Ich möchte meine Programme immer so einfach und verständlich, wie möglich halten. Warum soll ich etwas kontruieren, was die Sprache nicht kann? Das macht doch alles unnötig kompliziert.

    Klassen verwende ich, wenn ich objektorientiert programmieren möchte. Dann brauche ich aber auch Interfaces, Instanzen, Vererbung. Polymorphie und was mir die objektorientierte Programmierung sonst noch so bietet.

    Wenn ich objektorientiert Programmieren möchte, dann nehme ich eine geeignete Sprache.

    Bitte melde dich an, um diesen Link zu sehen.

    Einmal editiert, zuletzt von detlef (17. Dezember 2023 um 21:50)

  • Man kann das alles machen, aber wozu?

    Weil man mit z88dk und CC65 leistungsfähige C-Cross-Compiler für ZX Spectrum und C64/Atari800/Commander X16 hat.

    Ob es dafür C++-Cross-Compiler gibt, weiß ich nicht.

    Außerdem ist mir persönlich C++ bisher nicht so sympathisch.

    Ich würde gern lieber in C schreiben. Aber die Möglichkeit, solche Pseudo-Objekte mit all den Daten und Methoden "darin", in ihrer Gesamtheit an Funktionen zu übergeben und diese dort auf einfache Weise weiterzuverarbeiten, erscheint mir außerordentlich nützlich.

    In meinen Programmen hab' ich vor allem diese OOP-Grundfunktionalität verwendet. Vererbung und Polymorphismus dagegen nur sehr selten. Die sind mir daher nicht so wichtig.

    Was Diddl beschrieben hat, hatte ich in meinem Beispielprogramm auch schon zum großen Teil berücksichtigt.

    Da mir diese OOP-Grundfunktionalität aus Python-Programmen bereits sehr geläufig ist, würde sie meine C-Programme für mich selbst auch nicht verkomplizieren. Im Gegenteil: Der Zugriff auf solche "Pseudo-Objekte" wird Vieles sehr viel einfacher machen.

    Das könnte gerade die Art von Vereinfachung sein, die ich beim Schreiben von C-Programmen brauche, so daß ich die Hoffnung habe, dadurch mit C endlich mal voranzukommen.

  • Warum soll ich etwas kontruieren, was die Sprache nicht kann?

    Weil man, wenn man sauber programmiert, ggf. sowieso so programmieren wuerde. Objektorientierung ist ja erstmal nur ein Paradigma, die Sprache selbst muss das nicht zwingend koennen. Es wurde aber natuerlich irgendwann in manche Sprachen eingebaut, wie z.B. dann eben C++ (was zu Beginn "C mit Klassen" genannt wurde).

    Es gibt uebrigens auch unzaehlige C-Libs, die diesem Paradigma folgen. Ist also nix ungewoehnliches und auch keine neue Entdeckung.

    Sogar Vererbung ist ein stueckweit moeglich, da eine Funktion, die einen Struct-Pointer uebergeben bekommt, auch mit davon abgeleiteten Structs funktioniert. Nur virtuelle Methoden usw erfordern dann noch etwas mehr Arbeit, da weiss ich aber aus dem Stegreif auch nicht, wie man das nachbilden wuerde. Bzw ich kanns mir ein stueckweit denken, aber ich muesste da trotzdem erstmal rumprobieren.

    - neue Spiele für den C64 -
    Bitte melde dich an, um diesen Link zu sehen.Bitte melde dich an, um diesen Link zu sehen.Bitte melde dich an, um diesen Link zu sehen.Bitte melde dich an, um diesen Link zu sehen.

  • Es gibt uebrigens auch unzaehlige C-Libs, die diesem Paradigma folgen. Ist also nix ungewoehnliches und auch keine neue Entdeckung.

    Das ist richtig, aber in meinen Büchern zu C einschließlich K&R hab' ich's nicht gefunden. Deshalb war es für mich ein "Aha"-Erlebnis.

    Oder im "Bitte melde dich an, um diesen Link zu sehen.": Steht das da?

    Ich sehe es nicht. Warum schreibt man das da nicht rein?

  • Man kann das alles machen, aber wozu?

    Weil man mit z88dk und CC65 leistungsfähige C-Cross-Compiler für ZX Spectrum und C64/Atari800/Commander X16 hat.

    Ob es dafür C++-Cross-Compiler gibt, weiß ich nicht.

    Außerdem ist mir persönlich C++ bisher nicht so sympathisch.

    Ich würde gern lieber in C schreiben. Aber diese Möglichkeit, solche Pseudo-Objekte mit all den Daten und Methoden "darin", an Funktionen zu übergeben und damit dort auf einfache Weise weiterzuverarbeiten, erscheint mir außerordentlich nützlich.

    Wenn es für ein System keine einsetzbare objektorientierte Sprache gibt, dann programmiere ich einfach prozedural. Auch da habe ich meine Datenobjekte (Instanzen) und Prozeduren, die sie verarbeiten. Die Projekte auf den 8-Bittern habe eine überschaubare Größe. Da bietet mir der pseudo-objektorientierte Ansatz keine wirklichen Vorteile.

    Ausserdem widerstrebt es mir, an eine Funktion Daten zu übergeben, die sie gar nicht braucht (Verletzung eines Cleancode-Prinzips). Bei kleinen Beispielen mag das ganz gut funktionieren, aber wenn das Programm etwas umfangreicher wird, bezweifle ich, dass man den Überblick behält.

    Bei einer Klasse ist das anders. Die muss in sich konsistent arbeiten.

    Du kannst ja mal von deinen Erfahrungen berichten, wenn du das erste Projekt programmiert hast.

  • Warum soll ich etwas kontruieren, was die Sprache nicht kann?

    Weil man, wenn man sauber programmiert, ggf. sowieso so programmieren wuerde. Objektorientierung ist ja erstmal nur ein Paradigma, die Sprache selbst muss das nicht zwingend koennen. Es wurde aber natuerlich irgendwann in manche Sprachen eingebaut, wie z.B. dann eben C++ (was zu Beginn "C mit Klassen" genannt wurde).

    Das ist dann eine etwas eingeschränkte Sicht auf Objektorientierung. Wie machst du das mit Interfaces und Vererbung? Baust du das dann auch alles nach?

  • next_i

    Ja das mag sein, zudem duerfte das K&R-Buch auch noch etwas zu alt dafuer sein. Der OOP-Hype kam ja erst in den 90ern oder so. Aber z.B. bekannte Libs wie Gtk oder SDL sind auch so aufgebaut, gibt bestimmt noch viele mehr, aber die beiden fallen mir da jetzt so spontan ein. Habe mit C auch nicht sooo viel am Hut, das koennen andere bestimmt besser erklaeren. Fakt ist aber, dass Objektorientierung nicht zwingend von der Sprache unterstuetzt werden muss, sondern es sich dabei eben, wie gesagt, um ein Paradigma handelt, also eine Art und Weise, wie man programmiert, bzw. wie man die Architektur aufbaut/betrachtet.

    - neue Spiele für den C64 -
    Bitte melde dich an, um diesen Link zu sehen.Bitte melde dich an, um diesen Link zu sehen.Bitte melde dich an, um diesen Link zu sehen.Bitte melde dich an, um diesen Link zu sehen.

  • Du kannst ja mal von deinen Erfahrungen berichten, wenn du das erste Projekt programmiert hast.

    Das will ich gerne tun. Bisher hab' ich aber noch keine nennenswerten C-Projekte, weil es mir wie gesagt schwerfällt, damit zurechtzukommen. In Python sieht's besser aus, da hab' ich gerade Bitte melde dich an, um diesen Link zu sehen. hochgeladen.

    Aber wie gesagt, wenn mir was in C gelingen sollte, sag' ich Bescheid. ;)

  • Warum soll ich etwas kontruieren, was die Sprache nicht kann?

    Weil man, wenn man sauber programmiert, ggf. sowieso so programmieren wuerde. Objektorientierung ist ja erstmal nur ein Paradigma, die Sprache selbst muss das nicht zwingend koennen. Es wurde aber natuerlich irgendwann in manche Sprachen eingebaut, wie z.B. dann eben C++ (was zu Beginn "C mit Klassen" genannt wurde).

    Das ist dann eine etwas eingeschränkte Sicht auf Objektorientierung. Wie machst du das mit Interfaces und Vererbung? Baust du das dann auch alles nach?

    Kommt drauf an, wie intensiv man es benoetigt. Wie ich schon sagte, funktioniert Vererbung ein stueckweit auch schon von Haus aus - denn die Basis-Methoden funktionieren auch mit abgeleiteten Structs. Wenn Du Methoden jedoch ueberschreiben willst, musst Du in irgendeiner Form Sprung-Tables nachbauen. Wie man das nun am besten machen wuerde, weiss ich auch nicht, aber ich vermute da kann man was mit Funktions-Pointern machen.

    Wie gesagt, es gibt ja auch bekannte Libs, die nach dem Prinzip funktionieren. Die werden das schon irgendwie gelost haben :)

    - neue Spiele für den C64 -
    Bitte melde dich an, um diesen Link zu sehen.Bitte melde dich an, um diesen Link zu sehen.Bitte melde dich an, um diesen Link zu sehen.Bitte melde dich an, um diesen Link zu sehen.

  • Der Witz ist nun, daß man das relativ ähnlich auch in C schreiben kann. Hab' hier ein Beispiel gepostet.

    Hm.... Ich habe mir das Dokument mal angeschaut:

    The datatypes and names of the arguments that are passed to a function are written in round brackets after the name of the function, here "main()".

    As in this case no arguments are passed to "main", the brackets are empty.

    Das stimmt für C++, aber nicht für C. Um zu sagen, dass main() keine Argumente bekommt, muss man in C immer noch das (void) schreiben, weil es sonst nur "unspezifizierte" Argumente sind.

    Bitte melde dich an, um diesen Link zu sehen. are always ignored by the C-compiler.

    Ähm... Nö:

    Code
    while (x++ < 100)  {} // compiliert
    whi le (x++ < 100)  {} // compiliert nicht

    Also nicht "always".

    Anderes, wohl relevanteres Beispiel:

    Code
    int b = 123;
    int * p = &b;
    int a = 456;
    
    a = a / *p; /* a wird durch b geteilt */
    a = a/*p; /* hier wird das "p;" auskommentiert; das ist wohl nicht ganz das gleiche */

    "int" usually means "signed long int".

    Nein! "int" ist "signed int". Das kann - je nach Compiler - identisch zu "signed short int" oder "signed long int" sein, muss aber nicht!

    Entsprechend muss bei "int" als printf()-Spezifier "%d" stehen; bei "long int" muss es "%ld" sein. Wenn der Compiler nun so ist, dann long int == int ist, dann mag das funktionieren, du begibst dich aber auf Glatteis.

    Bei "char" fehlt darüber hinaus, dass "char", "signed char" und "unsigned char" in C drei verschiedene Datentype sind, weil "char" ohne Signedness-Specifier anders gehandhabt wird als die anderen int-Typen, die ohne Specifier immer "signed" sind.

    Zitat von https://hlubenow.lima-city.de/c.html#13

    Conditions for the preprocessor can be programmed with:

    Code
    #if
    #if defined
    #ifdef
    ...

    Halte ich so ungünstig. Bitte melde dich an, um diesen Link zu sehen. und Bitte melde dich an, um diesen Link zu sehen. sind Präprozessor-Anweisungen. Das "defined" hinter "Bitte melde dich an, um diesen Link zu sehen." ist aber Teil eines Ausdrucks, der komplizierter sein kann. Du schmeisst hier Birnen und Äpfel in einen Topf (zumal nach dem defined noch Klammern kommen müssen, die du noch wegläßt).

    Zitat von https://hlubenow.lima-city.de/c.html#13

    #include-Statements

    There is another kind of preprocessor directive, that uses the keyword "Bitte melde dich an, um diesen Link zu sehen.". The "Bitte melde dich an, um diesen Link zu sehen." directive is followed by the name of a text-file, either in "< ... >" or in quotation marks (" ... "). When the preprocessor reaches that line, it searches for the file of the given filename. When the file is found, the preprocessor replaces the line of the directive with the whole content of that file. That way, (the header files of) libraries are imported into the source code.

    • If the filename is passed within quotation marks, the same directory as the source code is searched for the file.
    • If the filename is passed within "< ... >", several directories are searched for the file. The default directories to be searched by gcc are compiled into the program "cpp" and can be listed with the command "cpp -v". Usually, "/usr/include" and "/usr/local/include" are searched. Other directories can be passed to gcc using the "-I"-command.

    Der Standard ist da etwas generischer als es hier dargestellt ist. Im Übrigen sucht der C-Compiler auch bei Anführungszeichen in den Verzeichnissen, die er bei "<...>" durchsucht, falls die Datei lokal nicht existiert.

    Zitat von https://hlubenow.lima-city.de/c.html#14

    Variables can be declared as "const". That means, they can't be altered afterwards. Therefore, the initialization has to be in the same line as the definition:

    Code
    #include <stdio.h>
    
    int main() {
        const int a = 10;
        /* a = 15; Wouldn't work */
        printf("%d\n", a);
        return 0;
    }

    Variables declared that way are stored in a read-only-area of the program's memory.

    Nein, das kann const nicht leisten. Siehe zum Beispiel hier Bitte melde dich an, um diesen Link zu sehen. und viele andere Stellen.

    Zitat von https://hlubenow.lima-city.de/c.html#17

    gcc allows to combine all three: "int a = 10;". But other compilers (CC65. Maybe Borland, if I remember correctly) demand, that all declarations have to be written at the beginning of a function, and the initializations have to follow separately after all declarations:

    Code
    void main() {
        int a;
        int b;
        float c;
        char d[50];
        a = 5;
        b = 10;
    }

    Das ist so nicht richtig. Auch cc65 und BC erlauben es, Variablen bei der Definition direkt zu initialisieren.

    Was sie nicht erlauben, zumindest nich per Default, dass man zuerst Anweisungen schreibt und dann Variablen definiert. Das liegt daran, dass das erst mit C99 (also in der Sprachverson von 1999) eingeführt wurde, um mit C++ gleichzuziehen, und die Compiler zumindest in großen Teilen diesen Sprachstandard noch nicht können. Wenn du den gcc in den c89 oder c90 - Modus zurückschaltest, dann kann er das auch nicht.


    Darüber hinaus finde ich deine Beispiele ungünstig für C:

    • for-Loop von 1 bis 10 (statt von 0 bis 9)
    • break and continue: In einer Schleife von 1 bis 1000, wo bei 3 eine Sonderbehandlung passiert und bei 10 über "break" abgebrochen wird.

    Ich habe mir den Text nur auszugsweise angeschaut, da schlummern bestimmt noch ein Haufen weiterer Probleme.

  • Fakt ist aber, dass Objektorientierung nicht zwingend von der Sprache unterstuetzt werden muss, sondern es sich dabei eben, wie gesagt, um ein Paradigma handelt, also eine Art und Weise, wie man programmiert, bzw. wie man die Architektur aufbaut/betrachtet.

    Ich glaube, die Diskussion hatten wir schon öfter. ;)

    Du verwechselst vermutlich Objektorientierte Prorgrammierung mit Strukturierter Programmierung. Die Strukturierte Programmierung (Fachbegriff in der Informatik) stellt deutlich weniger Anforderungen an eine Programmiersprache. Man braucht zum Beispiel lokale und globale Variablen und bestimmte Kontrollstrukturen (also mit Commodore-Basic geht es nicht).

    Objektorientierte Programmierung ist in der Informatik ganz eindeutig definiert. Hier kannst du dir nochmal die Prinzipien durchlesen und mir dann erklären, wie du das alles in jeder Sprache realisieren willst,

    Bitte melde dich an, um diesen Link zu sehen.

    Man kann die Begriffe natürlich für sich selbst beliebig umdefinieren. Aber dann wird das eben schwierig in der Diskussion mit anderen.

  • Weil man mit z88dk und CC65 leistungsfähige C-Cross-Compiler für ZX Spectrum und C64/Atari800/Commander X16 hat.

    Ob es dafür C++-Cross-Compiler gibt, weiß ich nicht.

    Ich bin ein Freund von objektorientierter Programmierung.

    DA WO ES SINN MACHT.

    Funktionale Programmierung ist wunderbar, wo es nicht mehr braucht.

    Mit aller Gewalt objektorientiert zu arbeiten kann kontra produktiv sein!!

    ====

    Ich mag objektorientierte Programmierung in C.

    Es kapselt wunderbar alle Teile und macht sie mehrfach verwendbar.

    C++ hat sicher seine Berechtigung.

    Aber es birgt große Gefahren, speziell wenn man für sehr kleine Computer System entwickelt.

    Der Overhead ist gewaltig, wenn man nicht gut aufpasst.

    Wenn man neben C++ auch sehr fit in C ist, dann ist man sich viel bewusster welche Auswirkungen C++ Objekte bewirken.

    Man verwendet die Dinge nicht mehr so leichtfertig wie Entwickler, die nur mit C++ groß geworden sind und C kaum kennen.

    Speziell bei System mit wenig RAM und wenig Leistung kann C++ zum Verhängnis werden.

    Wenn man weiß was man tut und wie die Rädchen im Hintergrund laufen, dann ist C++ sehr okay.

    Bitte melde dich an, um diesen Link zu sehen. --- Bitte melde dich an, um diesen Link zu sehen. --- Bitte melde dich an, um diesen Link zu sehen.