C notwendig für C++?



  • TyRoXx schrieb:

    noexcept gibt es in C++. Man kann also durchaus gezielt Exceptions deaktiveren.

    Jo, wenn man dran denkt.

    TyRoXx schrieb:

    reinterpret_cast ist kein C-Feature.

    Die Unterscheidung ist in C++ nur notwendig, weil die Sprache viel zu viele Pitfalls hat.

    TyRoXx schrieb:

    for , if , else benutzt man in C++ auch.

    Nur zählt es dann nicht mehr als schönes C++.

    TyRoXx schrieb:

    std::vector darf realloc gerne für PODs benutzen.

    Nenn mir eine Standardbibliothek die das macht und wir reden weiter. Wieder ein Beispiel für der Performance schädliche Abstraktion.

    TyRoXx schrieb:

    memcpy benutzt man in C++ auch. Heißt aber std::copy .

    Ist aber oft langsamer, weil der Compiler nicht sicher sein kann, ob sich die Bereiche überlappen. (Kann er aber oft in konstruierten Codebeispielen.)

    TyRoXx schrieb:

    Abstraktionen nimmt man in C fast genau so wie in C++ vor. Es steht bloß weniger Syntactic Sugar zur Verfügung. Hast du jemals gutes C gesehen?

    Ja.
    Gutes C: Man erkennt genau, welche Schritte gemacht werden.
    Gutes C++: Sieht fancy aus, aber wie es funktioniert ist magic. Kennst du Boost.Spirit?

    TyRoXx schrieb:

    Zeiger benutzt man in C++ auch ständig in verschiedenen Varianten.

    Wie es auch in C refcounting gibt. Nur hat das Zeigermanagement in C++ viel zu viel Noise für meinen Geschmack. Vor allem die ständigen Konviertierungen mit .get()

    TyRoXx schrieb:

    fallschritt schrieb:

    Das Problem mit den Abstraktionen ist, dass man es nicht fine-tunen kann.
    Reihenfolge von zwei Destruktoraufrufen umkehren?

    Kann man mit optional machen oder durch swap mit einem Temporary.

    Was einen gratis default-Konstruktor voraussetzt. Aber predigt man in C++ nicht, dass Konstruktoren viel Logik enthalten können? Eine std::list z.B. macht im Default-Konstruktor Memory-Allokationen, damit gehen deine Hacks nicht.

    TyRoXx schrieb:

    Wenn du es unbedingt brauchst, kannst du auch deinen eigenen String schreiben, der dieselbe Schnittstelle wie std::string implementiert.

    Viel Spass beim Implementieren von >100 Memberfunktionen.

    TyRoXx schrieb:

    Kein Problem und völlig normal in C++.

    Habe auch das Gefühl, dass C++ sehr viel Boilerplate-Code bedeutet.

    TyRoXx schrieb:

    Außerdem hat C gar keinen String-Typ, also was hat das mit C zu tun?

    Für Immutable-Strings ist const char* ausreichend. Wenn man mehr mit Strings macht hat man sowieso ein GUI-Toolkit oder eine utf8-Library. Kann std::string mittlerweile Unicode?

    TyRoXx schrieb:

    fallschritt schrieb:

    Also alle Abstraktionen entfernen und alles eine Stufe low-leveliger schreiben. Aka C.

    Das nennt man "schlechtes C". In dieser sehr beliebten Programmiersprache ist zum Beispiel OpenSSL geschrieben. Hat super funktioniert.

    Dieser Code in OpenSSL ist einfach schlecht. Aber keine Angst, ich kenne genügend C++-Code der noch schlimmer aussieht. Würdest du auch nicht als schön bezeichnen.

    TyRoXx schrieb:

    fallschritt schrieb:

    Call by reference vs call by value.

    Gibt es in C auch. In C++ ist das überhaupt kein Problem durch die Referenzen.

    In C++ ist das ein Riesenproblem weil unscheinbarer Code riesige Kopien hervorrufen kann. Besonders schlimm ist, dass das Vergessen von " const& " den Code langsamer macht und nicht " const& " der Default ist.

    TyRoXx schrieb:

    fallschritt schrieb:

    Unnötige Kopien vs Aliasing-Probleme. Nur mal um ein Fallstrick zu nennen, C++ ist voll davon.

    Anscheinend so voll, dass du nur insgesamt zwei aufzählen kannst.

    Also eigentlich war das der gleiche Fallstrick nur ein anderer Aspekt.

    TyRoXx schrieb:

    virtual baut man in C ständig so nach, dass exakt der gleiche Code generiert wird. Siehe gutes C.

    Aber in C kann man das besser optimieren. Reihenfolge ändern. Devirtualisierung. Viel einfacher anpassbar als in C++.

    TyRoXx schrieb:

    Exceptions baut man in C ständig mit if nach.

    Exceptions setzt man auch in C++ extrem spärlich ein. Meiner Erfahrung nach kann man problemlos drauf verzichten. Bisschen mehr assert, bei den wenigen verbleibenden Fällen macht man halt ein if, aber das hätte man in C++ genauso gemacht, weil erwarteter Fehler.

    Bei fehlgeschlagenem malloc schreib ich mir ein Handler, da prüfe ich nicht jeden Aufruf.

    fallschritt schrieb:

    Der erste Schritt, C++ zu verstehen, ist, die einzelnen Sprachfeatures in C zu implementieren.

    Was genau hat man davon das Nachbauen in C zu machen, wenn man eigentlich C++ lernen möchte? Nach der Logik müsste man C++ eigentlich in BCPL nachbauen. Oder habe ich verpasst, dass aktuelle Prozessoren jetzt direkt C ausführen?[/quote]
    Es gibt ja so eine Sprachhighleveligkeitskeitskette in der Art: C++ -> C -> BCPL -> ASM
    Je mehr man darüber weiss, wie der Code eine Stufe darunter übersetzt wird, desto besser kann man darüber Performance-Aussagen machen.



  • fallschritt schrieb:

    TyRoXx schrieb:

    memcpy benutzt man in C++ auch. Heißt aber std::copy .

    Ist aber oft langsamer, weil der Compiler nicht sicher sein kann, ob sich die Bereiche überlappen. (Kann er aber oft in konstruierten Codebeispielen.)

    Stimmt, copy entspricht nicht memcpy .

    fallschritt schrieb:

    TyRoXx schrieb:

    Abstraktionen nimmt man in C fast genau so wie in C++ vor. Es steht bloß weniger Syntactic Sugar zur Verfügung. Hast du jemals gutes C gesehen?

    Ja.
    Gutes C: Man erkennt genau, welche Schritte gemacht werden.
    Gutes C++: Sieht fancy aus, aber wie es funktioniert ist magic. Kennst du Boost.Spirit?

    Spirit sähe in C viel schlimmer aus. Man würde gar nichts erkennen.

    fallschritt schrieb:

    TyRoXx schrieb:

    Zeiger benutzt man in C++ auch ständig in verschiedenen Varianten.

    Wie es auch in C refcounting gibt. Nur hat das Zeigermanagement in C++ viel zu viel Noise für meinen Geschmack. Vor allem die ständigen Konviertierungen mit .get()

    Warum brauchst du denn ständig die nackten Zeiger?

    fallschritt schrieb:

    TyRoXx schrieb:

    fallschritt schrieb:

    Das Problem mit den Abstraktionen ist, dass man es nicht fine-tunen kann.
    Reihenfolge von zwei Destruktoraufrufen umkehren?

    Kann man mit optional machen oder durch swap mit einem Temporary.

    Was einen gratis default-Konstruktor voraussetzt. Aber predigt man in C++ nicht, dass Konstruktoren viel Logik enthalten können? Eine std::list z.B. macht im Default-Konstruktor Memory-Allokationen, damit gehen deine Hacks nicht.

    Eigentlich predigt man, dass Konstruktoren gar keine Logik enthalten sollen, sondern höchstens eine Initialisierungsliste, die nur initialisiert.
    std::list sollte man generell nicht verwenden. Im Fall der Liste reicht aber ein clear , um die Elemente freizugeben. Da braucht es keinen kostenlosen Standardkonstruktor.

    fallschritt schrieb:

    TyRoXx schrieb:

    Wenn du es unbedingt brauchst, kannst du auch deinen eigenen String schreiben, der dieselbe Schnittstelle wie std::string implementiert.

    Viel Spass beim Implementieren von >100 Memberfunktionen.

    TyRoXx schrieb:

    Kein Problem und völlig normal in C++.

    Habe auch das Gefühl, dass C++ sehr viel Boilerplate-Code bedeutet.

    Du hast mit std::string angefangen. Heute würde man den nicht mehr so entwerfen.

    fallschritt schrieb:

    TyRoXx schrieb:

    Exceptions baut man in C ständig mit if nach.

    Exceptions setzt man auch in C++ extrem spärlich ein. Meiner Erfahrung nach kann man problemlos drauf verzichten.

    Nein, kann man nicht. Das Ergebnis ist immer Müll. Jede Funktion gibt am Ende false , -1 oder nullptr im Fehlerfall zurück. Die eigentlichen Ergebnisse landen in irgendwelchen Membern von Gottklassen. Die Fehlerursachen werden meist als Alibi in überfüllte Log-Dateien geschrieben.

    fallschritt schrieb:

    Bisschen mehr assert, bei den wenigen verbleibenden Fällen macht man halt ein if, aber das hätte man in C++ genauso gemacht, weil erwarteter Fehler.

    assert ist orthogonal zu throw . Die haben nichts miteinander zu tun. Am Anfang fand man Ideen wie std::out_of_range akzeptabel, aber das ist lange vorbei.
    if zur Behandlung von Fehlern ist Zeitverschwendung. Dafür hat man die Exception erfunden.
    Ich sehe zwei sinnvolle Exception-Typen: bad_alloc und system_error . bad_alloc wirft man nie selbst. system_error werfen Bibliotheken, die C-Schnittstellen kapseln.
    Man sollte ansonsten noch davon ausgehen, dass fremder Code andere Exceptions werfen kann.
    Ein sauberes, modernes C++-Programm hat in der Regel ein try und ein catch . Jedes weitere sollte gut überlegt sein.
    Ein sauberes, modernes C-Programm hat unzählige if s, die fast immer nichts tun. Der Code muss geschrieben, gepflegt, korrigiert, getestet und ignoriert werden. Deswegen sind Exceptions besser.

    fallschritt schrieb:

    Bei fehlgeschlagenem malloc schreib ich mir ein Handler, da prüfe ich nicht jeden Aufruf.

    Und was kann der Handler dann machen außer den Prozess zu beenden? Genau das würde bad_alloc auch standardmäßig tun.



  • TyRoXx schrieb:

    fallschritt schrieb:

    Aber predigt man in C++ nicht, dass Konstruktoren viel Logik enthalten können? Eine std::list z.B. macht im Default-Konstruktor Memory-Allokationen, damit gehen deine Hacks nicht.

    Eigentlich predigt man, dass Konstruktoren gar keine Logik enthalten sollen, sondern höchstens eine Initialisierungsliste, die nur initialisiert.
    std::list sollte man generell nicht verwenden.

    Der Konstruktor muss alle Invarianten herstellen. Invarianten sind gut. Wenn ein Objekt damit man es verwenden kann immer eigenen Speicher braucht, dann sollte es eine Invariante "besitzt immer Speicher" haben, und diese muss im Konstruktor dann hergestellt werden. D.h. der Konstruktor muss Speicher anfordern.
    Oder ein File oder eine Datenbank-Verbindung, Transaktion - was auch immer aufmachen/erzeugen/...

    Natürlich kann man das alles in Form einer Initialisierungsliste schreiben - was ich i.A. auch für gut halte (zumindest so lange es keine übertrieben umständlichn Kunstgriffe/Hilfskonstrukte erfordert). Nur darum geht's "fallschritt" ja nicht. Es geht ihm darum dass der Konstruktor mehr macht als nur ein paar Werte mit Konstanten zu initialisieren. Bzw. um alles was dazu führt dass ein Objekt nicht mehr "memcpy-kopierbar" ist.

    Und solche Fälle hast du bei RAII *dauernd*. Weil es das ist was RAII ausmacht.

    D.h. wörtlich genommen stimmt das schon was du schreibst, hat dann aber mit der Aussage von "fallschritt" nichts zu tun. Oder, wenn man es so interpretiert dass es ein Bezug zur Aussage von "fallschritt" entsteht, dann stimmt was du schreibst überhaupt nicht. Weil es dann gleichbedeutend wäre mit "man predigt kein RAII zu verwenden".



  • hustbaer schrieb:

    Wenn ein Objekt damit man es verwenden kann immer eigenen Speicher braucht, dann sollte es eine Invariante "besitzt immer Speicher" haben, und diese muss im Konstruktor dann hergestellt werden. D.h. der Konstruktor muss Speicher anfordern.

    Falsch, der Konstruktor bekommt einen Parameter für den Speicher, den der Aufrufer zu besorgen hat.

    EDIT: Das gilt natürlich eher für High-Level-Code. Konstruktoren auf der niedrigsten Ebene wie std::vector<T>::vector(InputIterator, InputIterator) dürfen Speicher anfordern, weil es ihre einzige Aufgabe ist. Man sieht aber am Uniform-Initialization-Desaster, dass namenlose Konstruktoren selbst für Experten schwer zu beherrschen sind. Vielleicht wäre std::make_vector sinnvoller gewesen.

    Die Konstruktoren hätten am besten so ausgesehen:

    vector();
    vector(vector const &);
    vector(vector &&);
    template <class InputIterator>
    vector(InputIterator, InputIterator, Alloc);
    
    //bzw.
    template <class InputRange>
    explicit vector(InputRange, Alloc);
    

    Alle anderen kann man mit freien Funktionen nachbauen.

    template <class T>
    some-range-type<T> n_times(T const &, std::size_t);
    
    template <class T, class Alloc>
    std::vector<T> make_vector(std::initializer_list<T>, Alloc);
    

    hustbaer schrieb:

    Oder ein File oder eine Datenbank-Verbindung, Transaktion - was auch immer aufmachen/erzeugen/...

    Genau das gehört eben nicht in Konstruktoren.

    //schlecht, weil
    // - der Konstruktor keinen eigenen Namen hat
    // - man höllisch mit Exceptions aufpassen muss
    // - die Öffnen-Operation ohne jeden Grund untrennbar mit dem Wrapper um ein einfaches Handle verbunden ist (zwei orthogonale Aufgaben in einer Klasse)
    file::file(boost::filesystem::path const &path);
    
    //besser
    file::file(implementation-defined-handle) noexcept;
    file open(boost::filesystem::path const &path);
    


  • fallschritt schrieb:

    Zu einen kennst Du Dich recht wenig aus.
    Zum anderen solltest Du Dir überlegen, ob Du hier richtig bist, wenn Du nur C++-bashen willst.



  • Ach TyRoXx, du weisst wie immer alles besser -- wenn auch leider wie so oft nicht wirklich.
    Anstatt dass du versuchst zu verstehen was ich gemeint habe, plauderst du Hollunder über Dinge um die es gar nicht geht.

    TyRoXx schrieb:

    hustbaer schrieb:

    Wenn ein Objekt damit man es verwenden kann immer eigenen Speicher braucht, dann sollte es eine Invariante "besitzt immer Speicher" haben, und diese muss im Konstruktor dann hergestellt werden. D.h. der Konstruktor muss Speicher anfordern.

    Falsch, der Konstruktor bekommt einen Parameter für den Speicher, den der Aufrufer zu besorgen hat.

    Geht's dir hier um Inversion of Control oder um was?
    Ich rede hier von RAII Klassen, da sind wir üblicherweise ziemlich weit unten. Da du aber anscheinend davon ausgehst dass ich grundsätzlich Müll schreibe, und daher bei zwei Möglichen Auslegungen davon ausgehst dass ich nur die gemeint haben kann wo du was bekritteln kannst...
    Tjoah.

    TyRoXx schrieb:

    Man sieht aber am Uniform-Initialization-Desaster, dass namenlose Konstruktoren selbst für Experten schwer zu beherrschen sind. Vielleicht wäre std::make_vector sinnvoller gewesen.

    Ob jetzt namenlos oder als benannte Funktion spielt eigentlich keine Rolle. Wobei es auch ein Fehler ist alles grundsätzlich über Factory Funktionen zu machen. Wenn Objekte nicht oder nur teuer zu kopieren bzw. zu moven sind, kann das nämlich ziemlich lästig werden.

    TyRoXx schrieb:

    hustbaer schrieb:

    Oder ein File oder eine Datenbank-Verbindung, Transaktion - was auch immer aufmachen/erzeugen/...

    Genau das gehört eben nicht in Konstruktoren.

    Blah.

    TyRoXx schrieb:

    //schlecht, weil
    // - der Konstruktor keinen eigenen Namen hat
    

    Na dann gib ihm halt einen wenn du meinst es muss sein. Macht keinen Unterschied. Wichtig ist nur dass man über das public Interface kein nicht-initialisiertes Objekt bekommen kann.

    TyRoXx schrieb:

    // - man höllisch mit Exceptions aufpassen muss
    

    Blödsinn. Das mag für Leute die es nicht versucht haben so aussehen. Ist aber nicht so. Man muss sich bloss an ein paar recht einfache Regeln halten, dann bekommt man mit Exceptions kein Problem.

    TyRoXx schrieb:

    // - die Öffnen-Operation ohne jeden Grund untrennbar mit dem Wrapper um ein einfaches Handle verbunden ist (zwei orthogonale Aufgaben in einer Klasse)
    file::file(boost::filesystem::path const &path);
    

    Bullshit erster Güte.
    Erzeugen und Freigeben sind alles andere als orthogonal.
    Weitere Wrapper-Funktionen sind wieder eine andere Sache, die kann man natürlich gerne in einer eigenen Klasse machen.



  • Ethon schrieb:

    [...] nur weil es std::string gibt bedeutet das imho nicht dass man keine Ahnung haben sollte wie man in C mit Strings umgehen würde.

    Kommt echt drauf an, was du genau mit "in C mit Strings umgehen" meinst. Es behauptet jedenfalls keiner, dass das Wissen darüber, wie std::string intern funktioniert, wertlos ist. Es behauptet keiner, dass man die Schnittmenge von C und C++ nicht lernen soll. Es wird nur behauptet, dass man nicht die komplette Schnittmenge vor dem Rest von C++ lernen sollte. Warum? Weil das dann eine bescheierte Reihenfolge wäre! Weil das am Anfang z.B. mit der Low-Level-String-Frickelei zu nervig wäre. Weil man sich damit einen potentiell schlechten Stil angewöhnen könnte; denn das hat mit modernem C++ wirklich nichts zu tun, wenn man überall mit char*, strcpy, strlen, strdup, ... hantiert -- so wie man das ja anfangs "gelernt" hat!

    Ich hoffe für Euch, dass ihr in C für größere Projekte wenigstens einen vernünftigen std::string Ersatz nachbaut oder bei Git klaut oder so.



  • hustbaer schrieb:

    Geht's dir hier um Inversion of Control oder um was?

    Nein, um Parameterübergabe.

    hustbaer schrieb:

    TyRoXx schrieb:

    Man sieht aber am Uniform-Initialization-Desaster, dass namenlose Konstruktoren selbst für Experten schwer zu beherrschen sind. Vielleicht wäre std::make_vector sinnvoller gewesen.

    Ob jetzt namenlos oder als benannte Funktion spielt eigentlich keine Rolle. Wobei es auch ein Fehler ist alles grundsätzlich über Factory Funktionen zu machen. Wenn Objekte nicht oder nur teuer zu kopieren bzw. zu moven sind, kann das nämlich ziemlich lästig werden.

    Die Art von "Factory", die ich mit open skizziert habe, ersetzt doch gar nicht den Konstruktor. Sie zieht bloß Dinge heraus, die da nicht rein müssen, wie das Wrappen plattformabhängiger C-Schnittstellen. Der Konstruktor macht dann nur noch das nötigste. Was das mit Kopierbarkeit oder Move-barkeit zu hat, verstehe ich jetzt nicht.

    hustbaer schrieb:

    TyRoXx schrieb:

    hustbaer schrieb:

    Oder ein File oder eine Datenbank-Verbindung, Transaktion - was auch immer aufmachen/erzeugen/...

    Genau das gehört eben nicht in Konstruktoren.

    Blah.

    struct file
    {
    	file();
    
    	//Datei öffnen kann man noch halbwegs nachvollziehen
    	explicit file(boost::filesystem::path const &);
    
    	//aber wie mache ich eine Pipe? Wo kommt das zweite Handle hin?
    	explicit file(???);
    };
    

    Mit freien Funktionen kein Problem:

    struct pipe
    {
    	file in, out;
    
    	//der Standardkonstruktor initialisiert die Member natürlich leer
    	pipe();
    };
    
    pipe make_pipe();
    

    Bei diesem Beispiel sieht man auch eine gewisse Symmetrie. Oder hätte pipe() eine Pipe erzeugen sollen? Really?

    hustbaer schrieb:

    TyRoXx schrieb:

    // - man höllisch mit Exceptions aufpassen muss
    

    Blödsinn. Das mag für Leute die es nicht versucht haben so aussehen. Ist aber nicht so. Man muss sich bloss an ein paar recht einfache Regeln halten, dann bekommt man mit Exceptions kein Problem.

    In einer freien Funktion mit mehreren lokalen Variablen ist es ein wenig einfacher den Überblick über die möglichen Pfade zu behalten als in einer Klasse mit mehreren Membern.

    hustbaer schrieb:

    TyRoXx schrieb:

    // - die Öffnen-Operation ohne jeden Grund untrennbar mit dem Wrapper um ein einfaches Handle verbunden ist (zwei orthogonale Aufgaben in einer Klasse)
    file::file(boost::filesystem::path const &path);
    

    Bullshit erster Güte.
    Erzeugen und Freigeben sind alles andere als orthogonal.

    Komisch, dass unique_ptr dann keinen Konstruktor hat, der new aufruft.

    kkaw schrieb:

    Ich hoffe für Euch, dass ihr in C für größere Projekte wenigstens einen vernünftigen std::string Ersatz nachbaut oder bei Git klaut oder so.

    Aber nicht doch, das wäre viiiel zu ineffizient 🤡



  • Vielleicht ist es aber auch mal wichtig einen Blick über den Tellerrand zu wagen, um zu sehen wie sich die Sprache von der anderen Sprache unterscheidet, welche Tücken sie hat, welches Gruselkabinett sich damit programmieren lässt und wie man es besser macht. Es darf bloß nicht der Aha Effekt fehlen.

    Solange man die Stärken und die Schwächen lernt, ist alles in Ordnung. Man sollte aber nicht meinen, dass man mit seiner geliebten Sprache alles perfekt programmieren kann...

    Es gbt genügend C Programmierer, welche einigermaßen hacken können, aber meinen sie könnten damit C++ programmieren obwohl sie keinerlei Konzepte von C++ verstanden haben. So musste ich mal einem Kollegen das Konzept einer Klasse (maximale Kohäsion, minimale Kopplung) erklären. -> http://phdcomics.com/comics/archive.php?comicid=1689. Auch so manche Vererbung in C endetet schon in Copy-Paste-Modify.

    ---

    Kleiner Auszug aus meinem Gruselkabinett:

    // So programmierte ich Anfangs in C++, da man es in Java so machte
    tDataBase* GetNewDataBase(tDataBase db)  // In Java Copy-By-Reference, in C++ Copy-By-Value
    {
      tDataBase db2 = db;    
    
      db2.Key = "Hallo";
      return new tDataBase(db2);  // Wozu gibt es Garbage Collection?
    }
    
    // Danach musste ich ein C++ Projekt pflegen welches so aussah:
    class tTranslationPXYZJrotXZ : public tMovement
    {
      double GetValue(double* Array, double* ArrayIndizes)
      {
        return Array[ArrayIndizes[0]]*sin(Array[ArrayIndizes[2]])+
           sin(Array[ArrayIndizes[3]])*Array[ArrayIndizes[9]]+
           sin(Array[ArrayIndizes[2]])*Array[ArrayIndizes[1]]+
           sin(Array[ArrayIndizes[7]])*Array[ArrayIndizes[2]]+
           sin(Array[ArrayIndizes[1]])*Array[ArrayIndizes[3]]+
           sin(Array[ArrayIndizes[9]])*Array[ArrayIndizes[4]]);
      ]
    }
    
    // Danach programmierte ich C und die Programme sahen so aus:
    // c Datei
    
    int Open;
    FILE* File;
    char Line[80];   // Zeilen größer 80 Zeichen gibt es per Definition nicht!
    
    int SpecFile_Open(char* FileName)  // Mehrfachinstanziierung Fehlanzeige 
    {
      if (Open)
        return 1;
      File = fopen(FileName);
      Open = 1;
      // ....
      return 0;
    }
    
    int SpecFile_Open2(tFileHandle* Handle, char* FileName)  // Private Member Fehlanzeige 
    {
      Handle->File = fopen(FileName);
      Handle->Open = 1;
    }
    
    // Ach ja, folgendes habe ich auch schon gesehen:
    
    char* FileName;
    char Buffer[80];
    
    void SpecFile_Open();
    
    void SpecFile_ReadByte();
    
    void SpecFile_ReadLine();
    

Anmelden zum Antworten