Binärdatei schreiben und lesen
-
Hallihallo,
Ich versuche gerade für eine Übung Daten einer Schulklasse in eine Binärdatei zu schreiben und zu lesen.
Das schreiben scheint zu klappen, mit dem Lesen komm ich irgendwie gar nicht klar. Es funktioniert immer nur, wenn ich die Daten an die selbe Adresse einlese, von der ich sie auf die Datei geschrieben habe. Wann immer ich sie an eine andere Adresse lese (z.B eine Globale Variable oder ein anderes Attribut der Klasse) bekomme ich eine Fehlermeldung. Hab es schon mit einigem versucht.Das ist der compilierende Code. Scheint zu klappen und unten hab ich auch einfach mal testweise den namen oder das Datum ausgeben lassen nach dem einlesen. Er gibt das richtige aus, aber ich frage mich ob das nur Zufall ist.
void Person::datafromfile(){ ifstream zieldatei; zieldatei.open("c:/ziel.txt", ios::in|ios::binary); if(!zieldatei){ cerr << "Datei kann nicht geöffnet werden"; exit(-2); } zieldatei.read((char*)&Geburtsdatum, sizeof(Geburtsdatum)); zieldatei.read((char*)&vorname, sizeof(vorname)); zieldatei.read((char*)&name, sizeof(name)); cout << Geburtsdatum.getdate(); cout << name; } void Person::datatofile(){ ofstream zieldatei; zieldatei.open("c:/ziel.txt", ios::out|ios::binary); if(!zieldatei){ cerr << "Datei kann nicht geöffnet werden"; exit(-2); } zieldatei.write((char*)&Geburtsdatum, sizeof(Geburtsdatum)); zieldatei.write((char*)&vorname, sizeof(vorname)); zieldatei.write((char*)&name, sizeof(name)); }Vllt könnte mir jmd was zu dem read Befehl erklären. Weil scheinbar verstehe ich ihn nicht zu 100% und verstehe vor allem nicht warum er jedesmal meckert wenn ich ein anderes Attribut verwende, als das mit dem ich den Write befehl benutzt habe (datentypen stimmen natürlich dabei überein). Also z.B in Zeile 11 statt Name auf das Attribut Wohnort schreibe. Das klappt dann nicht.
-
Schlau wäre es gewesen, die Fehlermeldung und die Definition von Person inklusive der verwendeten Datentypen anzugeben.
Da Geburtsdatum eine Memberfunktion hat, würde ich stark vermuten, das sie kein POD ist und deshalb auch nicht mit read/write benutzt werden kann.
-
"Klappt nicht", "er meckert" und "ich bekomme eine Fehlermeldung" sind keine brauchbaren Problembeschreibungen.
-
SeppJ schrieb:
"Klappt nicht", "er meckert" und "ich bekomme eine Fehlermeldung" sind keine brauchbaren Problembeschreibungen.
Eigentlich gar keine richtige Fehlermeldung. Er bricht mitten im Programm, sobald er an diese Stelle kommt mit "Run failed" ab. Kompilieren tut es aber
-
manni66 schrieb:
Schlau wäre es gewesen, die Fehlermeldung und die Definition von Person inklusive der verwendeten Datentypen anzugeben.
Da Geburtsdatum eine Memberfunktion hat, würde ich stark vermuten, das sie kein POD ist und deshalb auch nicht mit read/write benutzt werden kann.class Person { public: Person(); Person(const Person& orig); virtual ~Person(); void setname(); void datafromfile(); void datatofile(); private: string vorname, name; string wohnort; enum Art geschlecht; Datum Geburtsdatum; };#include <cstdlib> #include <iostream> #include <string> #include <fstream> using namespace std; enum Monat { jan, feb, mrz, apr, mai, jun, jul, aug, sep, okt, nov, dez }; enum Art{ m, w }; class Datum { public: Datum(); Datum(const Datum& orig); virtual ~Datum(); void setdate(); short getdate(); private: short Tag; enum Monat mon; unsigned int Jahr; };
-
Wie andere Poster schon vermutet haben: Deine Klassen sind keine PODs.
-
SeppJ schrieb:
Wie andere Poster schon vermutet haben: Deine Klassen sind keine PODs.
Ich versteh das nicht so ganz. Wie mach ich denn daraus einen Pod? Soll ich etwa den Konstruktor leer lassen?
-
Wenn du den völlig sinnlosen und auch noch virtuellen Destruktor weglässt, wird das Datum funktionieren. Die string Member der Person allerdings nicht.
-
manni66 schrieb:
Wenn du den völlig sinnlosen und auch noch virtuellen Destruktor weglässt, wird das Datum funktionieren. Die string Member der Person allerdings nicht.
Ok jetzt versteh ich gar nichts mehr...
Und wieso ist der Destruktor sinnlos?
-
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...
-
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)
-
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?
-
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&bliest. Was macht denn Zeile 34 eigentlich?