Binärdatei schreiben und lesen


  • Mod

    Cabooze schrieb:

    Ich versteh das nicht so ganz. Wie mach ich denn daraus einen Pod?

    Keinen nicht-trivialen Konstruktor und Destruktor. Natürlich gilt das auch die anderen speziellen Memberfunktionen, wenn du sie hättest (Was die Frage aufwirft, welchen Sinn bei dir überhaupt Destruktor und Kopierkonstruktor haben, wenn du keinen Zuweisungsoperator hast? Das ist sehr ungewöhnlich und höchstwahrscheinlich sowieso falsch). Keine virtuellen Funktionen (und keine virtuellen Basisklassen, wenn du welche hättest). Keine (nicht-statischen) Member, auf die diese Kriterien nicht zutreffen (z.B. ist std::string kein POD).

    Soll ich etwa den Konstruktor leer lassen?

    Was steht da denn überhaupt drin, wenn du meinst, es einfach weglassen zu können? Das ist höchstwahrscheinlich nicht die Lösung deines Problems. Die richtige Lösung ist wohl, dass deine Daten eben einfach nicht geeignet sind, um mit read/write geschrieben zu werden und du dir eben was besseres einfallen lassen musst. So etwas wie eine beliebig lange Zeichenkette (std::string) ist eben nicht so einfach byteweise serialisierbar (woran sollte man das Ende erkennen?), das muss man schon irgendwie formatieren.



  • SeppJ schrieb:

    Cabooze schrieb:

    Ich versteh das nicht so ganz. Wie mach ich denn daraus einen Pod?

    Keinen nicht-trivialen Konstruktor und Destruktor. Natürlich gilt das auch die anderen speziellen Memberfunktionen, wenn du sie hättest (Was die Frage aufwirft, welchen Sinn bei dir überhaupt Destruktor und Kopierkonstruktor haben, wenn du keinen Zuweisungsoperator hast? Das ist sehr ungewöhnlich und höchstwahrscheinlich sowieso falsch). Keine virtuellen Funktionen (und keine virtuellen Basisklassen, wenn du welche hättest). Keine (nicht-statischen) Member, auf die diese Kriterien nicht zutreffen (z.B. ist std::string kein POD).

    Soll ich etwa den Konstruktor leer lassen?

    Was steht da denn überhaupt drin, wenn du meinst, es einfach weglassen zu können? Das ist höchstwahrscheinlich nicht die Lösung deines Problems. Die richtige Lösung ist wohl, dass deine Daten eben einfach nicht geeignet sind, um mit read/write geschrieben zu werden und du dir eben was besseres einfallen lassen musst. So etwas wie eine beliebig lange Zeichenkette (std::string) ist eben nicht so einfach byteweise serialisierbar (woran sollte man das Ende erkennen?), das muss man schon irgendwie formatieren.

    Der Konstructor generiert einfach ein paar Zufallszahlen für das Datum.

    Ach ja und der Kopierkonstruktor und Destruktor erzeug Netbeans automatisch.

    Also der Punkt ist der: Wir hatten eine Übungsaufgabe vom Prof. Da war das Struct Person und das Struct Datum. Dann noch die beiden enums (s.o). Daraus sollten wir eine Objektorientierte Variante machen und das in eine Binärdatei schreiben und wieder auslesen.
    Ich wüsste jetzt also nicht wie ich es sonst machen soll...


  • Mod

    Cabooze schrieb:

    Der Konstructor generiert einfach ein paar Zufallszahlen für das Datum.

    Und das kommt dir sinnvoll vor? Ist das eine inhärente Eigenschaft von Datumsangaben, dass sie zufällig sind?

    Ach ja und der Kopierkonstruktor und Destruktor erzeug Netbeans automatisch.

    Tja, dann kann man wohl nix machen. Wenn sich deine Programme von alleine schreiben, ohne dass du verstehen brauchst, was da vorgeht, dann hast du ja auch kein Problem, oder?

    Also der Punkt ist der: Wir hatten eine Übungsaufgabe vom Prof. Da war das Struct Person und das Struct Datum. Dann noch die beiden enums (s.o). Daraus sollten wir eine Objektorientierte Variante machen und das in eine Binärdatei schreiben und wieder auslesen.
    Ich wüsste jetzt also nicht wie ich es sonst machen soll...

    Dann ist es wahrscheinlich Teil der Aufgabe, das heraus zu finden. (Oder die Aufgabe ist falsch gestellt, was ich nicht ausschließen möchte. Oder du verheimlichst uns noch mehr Überraschungen, mit denen dein Professor gar nichts zu tun hatte, in der Art deiner virtuellen Konstruktoren)



  • SeppJ schrieb:

    Und das kommt dir sinnvoll vor? Ist das eine inhärente Eigenschaft von Datumsangaben, dass sie zufällig sind?

    Natürlich nicht. Aber warum spielt es für das Programm eine Rolle ob das Datum nun der 31.05.1998 oder der 17.01.203 ist? Es war ja nur zum testen gedacht. Klar ist es sinnlos. Egal ich hab jetzt den Konstruktor leer gemacht und lasse jetzt das Datum über einen setter eingeben. Ebenso wie Name und Vorname.

    SeppJ schrieb:

    Tja, dann kann man wohl nix machen. Wenn sich deine Programme von alleine schreiben, ohne dass du verstehen brauchst, was da vorgeht, dann hast du ja auch kein Problem, oder?

    Ich wusste bisher nicht, dass ein Destruktor oder ein Kopierkonstruktor mal ein Problem sein könnte. Ich hatte das auch immer so verstanden, dass eine Klasse immer unbedingt einen Destruktor braucht. Aber wenn ich das jetzt richtig verstanden habe sind die bei Memberfunktionen schwachsinnig, ne?

    SeppJ schrieb:

    Dann ist es wahrscheinlich Teil der Aufgabe, das heraus zu finden. (Oder die Aufgabe ist falsch gestellt, was ich nicht ausschließen möchte. Oder du verheimlichst uns noch mehr Überraschungen, mit denen dein Professor gar nichts zu tun hatte, in der Art deiner virtuellen Konstruktoren)

    Also die Aufgabe ist alle einträge des Arrays (Das aus Person-Objekten besteht) in eine Binärdatei zu lesen und zu schreiben. Bei ca 2 Folien die wir bloß über das Thema gemacht hatten und da einmal ein simples Array aus Doublewerten eingeschrieben/ausgelesen wurde und einmal ein komplettes Struct, bin ich davon ausgegangen, dass es mit Objekten ähnlich geht.

    Ich hab jetzt mal einen kleinen Versuch gestartet, ohne objekte oder sonst was.

    int main(int argc, char** argv) {
    
        int d = 10;
        int e;
    
        ofstream schreiben;
        schreiben.open("c:/ziel.txt", ios::out|ios::binary);
        if(!schreiben){
            cerr << "Datei existiert nicht";
            exit(-2);
        }
        schreiben.write((char*)&d,sizeof(d));
    
        ifstream lesen;
        lesen.open("c:/ziel.txt", ios::in|ios::binary);
        if(!lesen){
            cerr << "Datei existiert nicht";
            exit(-3);
        }
        lesen.read((char*)&e,sizeof(e));
    
        cout << e;
    
        return 0;
    }
    

    Das ist im Grund exakt so, wie es der Prof in seinen Vorlesungsfolien hat, mit dem Unterschied, dass ich es nicht auf die variable d sondern e lese. Und es funktioniert auch nicht...



  • Toller Fehler im Programm deines Profs: der Wert ist noch gar nicht in die Datei geschrieben, wenn er ausgelesen wird (weil Schreiben und Lesen gepuffert sind).

    Entweder noch

    schreiben.close();
    

    ausführen oder aber die gesamten "schreiben"-Zeilen in einen eigenen Block packen:

    {
        ofstream schreiben;
        schreiben.open("c:/ziel.txt", ios::out|ios::binary);
        if(!schreiben){
            cerr << "Datei existiert nicht";
            exit(-2);
        }
        schreiben.write((char*)&d,sizeof(d));
    }
    

    (so wird der Destruktor automatisch ausgeführt, welcher dann die Datei schließt und die gepufferten Daten wegschreibt - Stichwort: RAII)


  • Mod

    Cabooze schrieb:

    Ich wusste bisher nicht, dass ein Destruktor oder ein Kopierkonstruktor mal ein Problem sein könnte.

    Sie sind kein Problem. Das Problem ist, dass du auf einer Methode beharrst, die einfach nicht geht und die selbstdefinierten Konstruktoren und Destruktoren sind dabei nur ein kleiner Teil, warum das nicht geht. Und es ist ohnehin komisch, dass du sie selber definieren möchtest, denn:

    Ich hatte das auch immer so verstanden, dass eine Klasse immer unbedingt einen Destruktor braucht.

    Deswegen wird ja auch automatisch einer erzeugt, wenn du keinen eigenen angibst (ebenso bei einer ganzen Reihe anderer Memberfunktionen). Aber wenn du einen eigenen angibst, dann wird angenommen, dass du dir dabei was gedacht hast und da wichtiger Code drin steht. Deswegen spielt das eine Rolle, ob diese Funktionen existieren oder nicht, denn dann würde es keinen Sinn machen, sie einfach zu übergehen. Aber anscheinend hast du dir gar nichts gedacht, weswegen dies ein behebbares Hindernis ist.

    std::string hat aber trotzdem immer noch nicht-triviale Konstruktoren (und die anderen speziellen Memberfunktionen sind ebenfalls nutzerdefiniert) und die machen auch wichtige Sachen. Weswegen man eben keinen std::string einfach so aus dem Nichts zaubern kann, indem man ein paar Bytes setzt, ohne diese Funktionen aufgerufen zu haben.

    Aber wenn ich das jetzt richtig verstanden habe sind die bei Memberfunktionen schwachsinnig, ne?

    Häh?

    Und es funktioniert auch nicht...

    😡 Sind wir wieder am Anfang des Threads?

    SeppJ schrieb:

    "Klappt nicht", "er meckert" und "ich bekomme eine Fehlermeldung" sind keine brauchbaren Problembeschreibungen.

    Hätte ich noch ausdrücklich erwähnen müssen, dass "funktioniert nicht" ebenfalls keine brauchbare Fehlerbeschreibung ist? Leuten, die sich selber im Weg stehen, kann man einfach nicht helfen. (Aber vielleicht versuchst du mal, die gleiche Datei nicht mehrmals gleichzeitig zu öffnen)



  • Th69 schrieb:

    Toller Fehler im Programm deines Profs: der Wert ist noch gar nicht in die Datei geschrieben, wenn er ausgelesen wird (weil Schreiben und Lesen gepuffert sind).

    Entweder noch

    schreiben.close();
    

    ausführen oder aber die gesamten "schreiben"-Zeilen in einen eigenen Block packen:

    {
        ofstream schreiben;
        schreiben.open("c:/ziel.txt", ios::out|ios::binary);
        if(!schreiben){
            cerr << "Datei existiert nicht";
            exit(-2);
        }
        schreiben.write((char*)&d,sizeof(d));
    }
    

    (so wird der Destruktor automatisch ausgeführt, welcher dann die Datei schließt und die gepufferten Daten wegschreibt - Stichwort: RAII)

    Oh sorry. Nein das ist mein Fehler. Das close hab ich vergessen.
    Ok das war der Fehler. Dieses Programm funktioniert nun immerhin.



  • SeppJ schrieb:

    Cabooze schrieb:

    Ich wusste bisher nicht, dass ein Destruktor oder ein Kopierkonstruktor mal ein Problem sein könnte.

    Sie sind kein Problem. Das Problem ist, dass du auf einer Methode beharrst, die einfach nicht geht und die selbstdefinierten Konstruktoren und Destruktoren sind dabei nur ein kleiner Teil, warum das nicht geht. Und es ist ohnehin komisch, dass du sie selber definieren möchtest, denn:

    Ich hatte das auch immer so verstanden, dass eine Klasse immer unbedingt einen Destruktor braucht.

    Deswegen wird ja auch automatisch einer erzeugt, wenn du keinen eigenen angibst (ebenso bei einer ganzen Reihe anderer Memberfunktionen). Aber wenn du einen eigenen angibst, dann wird angenommen, dass du dir dabei was gedacht hast und da wichtiger Code drin steht. Deswegen spielt das eine Rolle, ob diese Funktionen existieren oder nicht, denn dann würde es keinen Sinn machen, sie einfach zu übergehen. Aber anscheinend hast du dir gar nichts gedacht, weswegen dies ein behebbares Hindernis ist.

    std::string hat aber trotzdem immer noch nicht-triviale Konstruktoren (und die anderen speziellen Memberfunktionen sind ebenfalls nutzerdefiniert) und die machen auch wichtige Sachen. Weswegen man eben keinen std::string einfach so aus dem Nichts zaubern kann, indem man ein paar Bytes setzt, ohne diese Funktionen aufgerufen zu haben.

    Aber wenn ich das jetzt richtig verstanden habe sind die bei Memberfunktionen schwachsinnig, ne?

    Häh?

    Und es funktioniert auch nicht...

    😡 Sind wir wieder am Anfang des Threads?

    SeppJ schrieb:

    "Klappt nicht", "er meckert" und "ich bekomme eine Fehlermeldung" sind keine brauchbaren Problembeschreibungen.

    Hätte ich noch ausdrücklich erwähnen müssen, dass "funktioniert nicht" ebenfalls keine brauchbare Fehlerbeschreibung ist? Leuten, die sich selber im Weg stehen, kann man einfach nicht helfen. (Aber vielleicht versuchst du mal, die gleiche Datei nicht mehrmals gleichzeitig zu öffnen)

    Also die Konstruktoren und Destruktoren sind ja nicht benutzerdefiniert (nicht mehr) und den Virtuellen kopierkonstruktor hab ich raus. Also sollte das dann(außer die string attribute) funktionieren?

    Und da man wie du sagst keine Strings einfach in eine Datei lesen und schreiben kann, wären doch dann nur c-strings die möglicihkeit oder?


  • Mod

    Cabooze schrieb:

    Und da man wie du sagst keine Strings einfach in eine Datei lesen und schreiben kann, wären doch dann nur c-strings die möglicihkeit oder?

    Kommt drauf an, was du unter "einfach" und "C-Strings" genau verstehst. Ein C-String ist nur eine Konvention, wie man in C das Ende einer Zeichenkette markiert, die unterliegende Datenstruktur (die ist es, auf die es ankäme, wenn du wirklich ganz naiv read/write benutzen willst) ist nicht genauer spezifiziert.



  • @Cabooze: Unter Disch's tutorial to good binary files gibt es einen Überblick über das Schreiben von Binärdateien. Unter "2) Define your complex types" werden anhand vom std::string-Datentyp drei verschiedene Optionen vorgestellt (wobei m.E. der 3. der beste Weg ist, also erst die Länge und dann den (evtl. noch nullterminierten) String wegschreiben).



  • Tut mir leid, ich krieg es immernoch nicht gebacken mit Strings.

    string d = "einstring";
        string b;
        int size = d.size();
    
        ofstream schreiben;
        schreiben.open("/cygdrive/c/bla.dat", ios::out|ios::binary);
    
        if(!schreiben){
            cerr << "Datei existiert nicht";
            exit(-2);
        }
    
        schreiben.write((char*)&size , sizeof(int));
        schreiben.write(d.c_str() , d.size());
    
        schreiben.close();
    
        ifstream lesen;
        lesen.open("/cygdrive/c/bla.dat", ios::in|ios::binary);
        cout << "6";
        if(!lesen){
            cerr << "Datei existiert nicht";
            exit(-3);
        }
    
        lesen.read((char*)&size , sizeof(int));
        lesen.read((char*)&b, d.size());
    
        lesen >> b;
    
        lesen.close();
    
        cout << b;
    

    Da ist immernoch irgendwas falsch. Das Programm bricht immernoch mit Run failed ab, bevor er überhaupt den Code liest.



  • Was jedenfalls in's Auge springt ist, dass Du d.c_str() wegschreibst und in &b liest. Was macht denn Zeile 34 eigentlich?



  • Furble Wurble schrieb:

    Was jedenfalls in's Auge springt ist, dass Du d.c_str() wegschreibst und in &b liest. Was macht denn Zeile 34 eigentlich?

    Ja Zeile 34 war so n test. Hätt ich vielleicht noch wegmachen sollen. Und &b wollte ich absichtlich lesen.
    Naja ich hab es jetzt anders gemacht. Das auslesen sieht bei mir nun so aus:

    ifstream lesen;
        lesen.open("/cygdrive/c/bla.txt", ios::in|ios::binary);
    
        if(!lesen){
            cerr << "Datei existiert nicht";
            exit(-3);
        }
    
        lesen.read((char*)&size2,sizeof(int)); // größe wird zuerst ausgelesen damit sie bekannt ist
        char wort[size2];
        lesen.read(wort, size2);
        for(int i = 0; i< size2; i++){
            b += wort[i];
        }
    

    Und endlich funktioniert es!

    Den Code zum einlesen hab ich gelassen. MIch wundert nur eins: Die Daten die "Binär" auf die Datei geschrieben sind, sind trotzdem lesbar. Müssten da nicht irgendwelche kryptischen zeichen stehen oder ist das normal?



  • Hmm, da war ich wohl etwas sparsam...

    Du kannst direkt in den String lesen. Nur musst Du Deine Daten halt dahin schreiben, wo der String seine Daten hält. Und dieser Puffer muss auch groß genug sein:

    lesen.read((char*)&size , sizeof(int));
        b.resize(size);  // Platz schaffen
        lesen.read(&b[0], size);  // Du siehst den Unterschied zu Deinem Code.
    


  • Das mit den Binärdaten ist normal. ios::binary bedeutet einfach, dass Zeilenumbrüche nicht konvertiert werden sollen. Der eigentliche Unterschied zwischen binär und formatiert liegt in der Ausgabemethode. ofstream::write schreibt die Daten so wie sie sind raus, im Falle von strings ASCII/UTF8, je nach Zeichensatz oder bei int s eben die 4/8 Bytes. Der << -Operator schreibt string auch als ASCII/UTF8 raus, int s allerdings auch in lesbarer Form als string .


Anmelden zum Antworten