Muss ich Vectoren serialisieren oder geht das auch irgendwie einfacher?



  • Hallo Badestrand,

    danke für diesen Programmauszug. Kann ich dir kurz erklären wie ich das verstehe, nur damit ich das in meinem Projekt auch richtig mache und richtig anwende.

    Struktur ist klar.

    ostream& operator<<( ostream& f, const XYZ& x )
    

    Hier beginnst du die Variable a in f zu schreiben, danach übergibst du die größe von vector b in f und gehst anschließend durch den vector <int> b und übergibst den inhalt an f.

    const_iterator bedeutet wohl, dass er stets den konstanten Abstand von int einhalten soll.

    danach machst du das selbe mit dem vector <vector <float> >c;
    und gibst f zurück.

    Einlesefunktion
    istream& operator>>( istream& f, XYZ& x )

    aus f wird a eingelesen, danach bestimmst du du die größe von b. Das size_type ist wohl eine bestimmter Datentyp der einem die Größe sagen kann, den wir in die Variable f geschrieben haben.

    vector<int>::size_type b_size;

    danach reservieren wir entsprechend viel Platz. und lesen unsere Int Elemente ein. ok soweit, das machen wir auch mit dem verschateltem Vector.

    Das verstehe ich soweit. Nur kommt nun meine Frage:

    Wie rufe ich das ganze auf?
    Ich habe leider keine Erfahrung mit Iteratoren und Container.

    Die Klassen habe ich als Beispiel um das erweitert, aber wie rufe ich diese ostream Funktion auf.

    friend ostream& operator<<( ostream& f, const XYZ& x )
    

    Dachte eigentlich das ich sowas mache:

    void XYZ::speichern(void)
    {
     fstream in_out("Test.dat", ios_base::in | ios_base::out);
    
     // Daten hineingeben
               //<<--------Hier sollte irgendwie dieses ostream& rein, aber ich bekomme den nicht aufgerufen.
    
     // Datei schließen
     in_out.close();
    }
    


  • egcactus schrieb:

    const_iterator bedeutet wohl, dass er stets den konstanten Abstand von int einhalten soll.

    Ein const_iterator ist ein Iterator, der auf konstante Daten verweist.

    egcactus schrieb:

    Wie rufe ich das ganze auf?
    Ich habe leider keine Erfahrung mit Iteratoren und Container.

    Der ganze Spaß ist eine Überladung der << und >> Operatoren für std::ostream und std::istream damit kannst Du die Daten "direkt" in und aus dem Stream schieben:

    XYZ xyz;
    std::fstream in_out("Test.dat", ios_base::in | ios_base::out);
    
    in_out << xyz;
    in_out >> xyz;
    

    Badestrand schrieb:

    Ohne Gewähr und Implementationsabhängig 🙂

    Was ist daran Implementationsabhängig?

    greetz, Swordfish



  • Swordfish schrieb:

    Was ist daran Implementationsabhängig?

    Ich weiß nicht, ob der Typ von vector<T>::size_type vorgeschrieben ist, weil wenn nicht, kann dieser Typ je nach Implementierung verschieden groß sein. Dann schreibt man mit dem von Compiler x erstellten Programm einen 8-Byte-int als Größe in die Binärdatei und liest einen 4-Byte-int mit einem von Compiler y erstellten Programm wieder aus. So in etwa und falls dem so sein sollte und so 🙂

    edit: Ach, und die Größe von normalen ints ist ja schon nicht festgelegt. Aber man könnte das ja umgehen, indem man fixe Größen rausschreibt, muss man halt nur entsprechend casten.



  • Ich mache irgend etwas falsch. Bei mir legt er nicht mal eine Datei an.

    Damit ich den Ablauf verstehe, habe ich mir ein Beispielprojekt angelegt. Das aus den Formularen besteht: Klassen.cpp / Klassen.h, Daten.cpp / Daten.h und Grundbild.cpp / Grundbild.hDas sieht nun so aus:

    Klassen.h

    class VectorDaten
    {
     private:
    
     public:
        VectorDaten(){}
        ~VectorDaten(){}
    
        // Variablen
        double                  a;
        vector<int>             b;
        vector< vector<float> > c;
        vector<float>           d;
    
        friend std::ostream& operator<<(std::ostream& f, const VectorDaten& x );
        friend std::istream& operator>>(std::istream& f, VectorDaten& x );
    
    };
    
    class TE       // Testklasse mit enthaltenen VectorDaten Klasse
    {
     private:
    
     public:
        TE(){}
        ~TE(){}
    
        class VectorDaten Daten;
    
        // Funktion zum Speichern
        void sichern(void);
        void laden(void);
    
        friend std::ostream& operator<<(std::ostream& f, const VectorDaten& x );
        friend std::istream& operator>>(std::istream& f, VectorDaten& x );
    
    };
    

    Klassen.cpp

    // Externe Daten einbinden
    extern class VectorDaten Haus;
    extern class TE Test;
    
    // Sichern Funktion
    void TE::sichern(void)
    {
     // Einrichten eines Ein / Ausgabe Pfades
     std::fstream in_out("C:\\Test.dat", ios_base::in | ios_base::out);
    
     // Daten hineingeben
     in_out <<  Test.Daten;
    
     // Dateiverbindung schließen
     in_out.close();
    }
    
    // Laden Funktion
    void TE::laden(void)
    {
     // Einrichten eines Ein / Ausgabe Pfades
     std::fstream in_out("C:\\Test.dat", ios_base::in | ios_base::out);
    
     // Daten hineingeben
     in_out >>  Test.Daten;
    
     // Dateiverbindung schließen
     in_out.close();
    }
    

    und im Grundbild habe ich folgendes:

    extern class VectorDaten Haus;
    extern class TE Test;
    
    TForm1 *Form1;
    //---------------------------------------------------------------------------
    __fastcall TForm1::TForm1(TComponent* Owner)
            : TForm(Owner)
    {
    }
    //---------------------------------------------------------------------------
    
    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
     // Datensatz
     Haus.b.push_back(1);
     Haus.b.push_back(2);
     Haus.b.push_back(3);
    
     Haus.d.push_back(2.5);
     Haus.d.push_back(3.5);
    
     Haus.c.push_back(Haus.d);
    
     // Datensatz in Klasse
     Test.Daten.b.push_back(100);
     Test.Daten.b.push_back(200);
     Test.Daten.b.push_back(300);
    
     Test.Daten.d.push_back(22.55);
     Test.Daten.d.push_back(33.55);
    
     Test.Daten.c.push_back(Haus.d);
    
    }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::Button2Click(TObject *Sender)
    {
     Test.sichern();
    }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::Button3Click(TObject *Sender)
    {
     Test.laden();
    }
    //---------------------------------------------------------------------------
    

    Speichern wird durchlaufen, es wird aber keine Datei angelegt, beim Laden bleibt bei er in_out >> Test.Daten hängen.

    Vermutlich, weil er ja auch keine Datei anlegt.

    Was mache ich falsch?



  • Ich habe meinen Fehler gefunden

    std::fstream in_out("C:\\Test.dat", ios_base::in | ios_base::out);
    

    Sollte einfach die Datei nicht zum Lesen und Schreiben gleichzeitig öffnen.

    std::fstream in_out("C:\\Test.dat", ios_base::out);
    

    Vielen Herzlichen Dank für eure Hilfe.
    Werde das ganze mal in mein Projekt einpflegen.

    Überglücklich dass, das funktioniert 😃

    Viele Grüße
    egcactus



  • Eine Frage ist jetzt noch aufgetreten:

    Ich habe eine Klasse CUOFreq mit einem Public Teil aus der Klasse CFreq. Wenn ich mein Speichern aufrufe, dann arbeitet er wunderbar den ostream von CUOFreq ab. Aber den Teil der Variablen die in CFreq stehen, werden nicht berücksichtigt. Kann ich die ostream Funktion in CUOFreq irgendwie erweitern, dass er zuerst die ostream Funktion aus CFreq abarbeitet und anschließend CUOFreq.



  • ich hab mir jetzt mal deine letzten Codeschnipsel angesehen, und ich glaub ich weis, wo dein Problem ist ;). du hast den operator<< bzw operator>> noch nicht ganz verstanden. Zumindest nicht so, wie er hier vorgeschlagne wurde.

    In der Stream Bibliothek von C++ hat man sich dazu entschlossen, das Speichern von Objekten mit einer uniformen Technik zu regeln, den stream operatoren. Dies ist natürlich keine Pflicht, aber man sollte es der übersichtlichkeithalber so machen.

    Die Idee dahinter ist, dass man jedes Objekt gleich speichern kann.

    //Foo ist irgendein beliebiger typ.
    class Foo foo;
    cout<<foo;
    

    das Objekt foo wird hierbei auf der Console ausgegeben. Natürlich kann man cout auch durch beliebige andere Stream Objekte austauschen.

    Damit das funktioniert, muss jedes Objekt das gespeichert werden soll einen stream operator anbieten.

    Nehmen wir zum Beispiel mal folgende einfache Struktur:

    struct Foo
    {
        int x,y,z;
    };
    
    //stream operator dazu
    ostream& operator<<(ostream& str,const Foo& foo)
    {
        str<<foo.x<<foo.y<<foo.z;
        return str;
    }
    
    //Anwendungsbeispiel:
    Foo foo1,foo2,foo3;
    //...
    cout<<foo1<<foo2<<foo3;
    
    //gleiches natürlich auch für operator>> spar ich mir aber erstmal.
    

    Wenn jetzt Foo Member eines anderen Objektes ist, möchte man Foo natürlich auch ausgeben können. Auch das ist nun sehr einfach:

    class Bar
    {
        private:
            Foo foo;
            Baz baz;
            int i;
            friend ostream& operator<<(ostream&,const Bar&);
        public:
            //...
    };
    ostream& operator<<(ostream& str,const Bar& bar)
    {
        str<<bar.foo<<bar.baz<<bar.i;
        return str;
    }
    

    Nun sollte auch klar sein, wie man die Stream operatoren verwendet, ich veränder dazu mal deinen Codeschnipsel:

    //Klassen.h
    //Vectordaten ist korrekt, also überspringen
    
    //...
    
    //Klasse TE
    //vorwärtsdeklarationen nicht vergessen, spar ich mir aber
    
    class TE       // Testklasse mit enthaltenen VectorDaten Klasse
    {
     private:
          VectorDaten daten;//interne Daten gehören nach private!
     public:
        TE(){}
        ~TE(){}
    
        // Funktion zum Speichern entfernt, da unnötig!
    
        //friend declaration geändert,alte war unnötig!
        //const correctness beachten!
        friend std::ostream& operator<<(std::ostream&,const TE&);
        friend std::istream& operator>>(std::istream&,const VectorDaten&);
    
    };
    
    //Klassen.cpp
    
    //...
    
    std::ostream& operator<<(std::ostream& str,const TE& o)
    {
        str<<o.daten;
        return str;
    }
    std::istream& operator>>(std::istream& str,const VectorDaten& o)
    {
        //leer zwecks übungszwecken
    }
    //...
    

    und dann noch:

    //---------------------------------------------------------------------------
    void __fastcall TForm1::Button2Click(TObject *Sender)
    {
        std::ofstream out("C:\\Test.dat");
        out<<Test;
    }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::Button3Click(TObject *Sender)
    {
     //leer zwecks übungszwecken
    }
    //---------------------------------------------------------------------------
    

    Jetzt solltest du deine Daten bequm speichern können.

    Aber wichtig: interne daten niemals public.



  • Hallo otze,

    danke für deine Erklärung. Ich hatte das in meinem Projekt so schon umgesetzt es war nur noch ein Fehler in meinem Testprogramm.

    Was ich meinte war: Das ich eine Klasse die bereits eine ostream Funktion hat an eine andere Klasse vererbe. Die erbende Klasse hat auch eine ostream Funktion.

    Wenn ich nun in der erbenden Klasse die ostream Funktion programmieren möchte, dann habe ich keinen Zugriff auf die geerbte Klasse.

    Hier das Beispiel:

    class CFreq
    {
     private: 
        int Anzahl;
        float Frequenz_Minimum;
    
     public:
      CFreq(){};
      ~CFreq(){};
    
     friend std::ostream& operator<<(std::ostream& f,const CFreq& Daten);
     friend std::istream& operator>>(std::istream& f,const CFreq& Daten);
    };
    
    class CUOFreq : public CFreq
    {
     private: 
        float Messschritt;
        float Frequenz_Maximum;
    
     public:
      CUOFreq(){};
      ~CUOFreq(){};
    
     friend std::ostream& operator<<(std::ostream& f,const CUOFreq& Daten);
     friend std::istream& operator>>(std::istream& f,const CUOFreq& Daten);
    };
    

    Dadurch das ich die Klasse CFreq an CUOFreq vererbe habe ich nur Zugriff auf die Public Funktionen.

    Wenn ich nun im ostream, der Klasse CUOFreq angebe

    f << Daten.Messschritt << Daten.Frequenz_Maximum;
    

    Werden mir die Daten gespeichert. Ich kann aber nicht angeben

    f << Daten.Anzahl << Daten.Frequenz_Minimum;
    

    Weil er darauf keinen Zugriff hat. Wenn ich aber die Klasse erzeuge, dann erzeuge ich ja.

    CUOFreq Bauteil;
    

    Da ich im Bauteil ja über die öffentlichen Schnittstellen zugriff auf CFreq habe ist das soweit ok. Nur beim Speichern hat er keinen Zugriff auf den ostream von CFreq und so werden nur float Messschritt; float Frequenz_Maximum gespeichert und die Anzahl und die Frequenz_Minimum stehen nicht in der Datei.

    Meine Frage ist nun, wie kann ich in CUOFreq einen Zugriff auf den ostream von CFreq bekommen. Also auf den ostream der geerbten Klasse.

    Grüße
    egcactus



  • Meine Frage ist nun, wie kann ich in CUOFreq einen Zugriff auf den ostream von CFreq bekommen. Also auf den ostream der geerbten Klasse.

    Also da gibt es erstmal mehrere möglichkeiten, aber du hast zuersteinmal ein anderes Problem.

    void print1(CuoFreq& freq)
    {
        cout<<freq;
    }
    void print2(CFreq& freq)
    {
        cout<<freq;
    }
    CuoFreq test;
    print1(test);
    print2(test);
    

    selbst wenn deine Operatoren ordentlich funktionieren würden, bekämst du 2 unterschiedliche ergebnisse. der erste Fall nutzt den operator<< von CuoFreq der zweite den von Freq, und die haben bei dir unterschiedliches verhalten. Sowas will niemand, vorallem nicht in einem polymorphen zusammenhang.

    Also korrigieren wir erstmal das:

    class CFreq
    {
     private:
        int Anzahl;
        float Frequenz_Minimum;
    
     protected:
         virtual void serialize(ostream& stream)const;
    
     public:
      CFreq(){};
      ~CFreq(){};
    
     friend std::ostream& operator<<(std::ostream& f,const CFreq& Daten);
     friend std::istream& operator>>(std::istream& f,const CFreq& Daten);
    }; 
    
    //in der cpp datei
    std::ostream& operator<<(std::ostream& f,const CFreq& Daten)
    {
         Daten.serialize(f);
    }
    void CFreq::serialize(ostream& f)
    {
        f<<Anzahl<<Frequenz_Minimum;
    }
    

    Washabe ich hier gemacht? ich hab deinen operator<< so verändert, dass er nun auf die virtuelle Funktion serialize verweist. Dadurch ist es dir erstmal möglich, dass du zumindest das verhalten anpassen kannst.

    Nun zu deinem ursprünglichen Problem:
    Nun, die Lösung ist durch die letzte Änderung auch sehr einfach. Wir müssen nur explizit auf die Basisklassenversion von serialize zugreifen.

    class CUOFreq : public CFreq
    {
     private:
        float Messschritt;
        float Frequenz_Maximum;
    
     public:
      CUOFreq(){};
      ~CUOFreq(){};
    }; 
    
    //in der cpp
    void CUOFreq::serialize(ostream& f)
    {
        //aufrufen der serialize funktion von CFreq
        CFreq::serialize(f);
        //ausgeben der neuen Daten.
        f<<Messschritt<<Frequenz_Maximum;
    }
    

    Allgemein musst du natürlich bei vererbung auch der abgeleiteten Klasse ein Interface geben, um die benötigten Daten zu bekommen. Du kannst zb eine protected get-funktion nutzen, die den wert der variablen zurück gibt. (falls dir das konzept der get und set funktionen unklar ist, da du ja auch die internen daten zuerst public gesetzt hattest in deinem startpost: eine getFunktion ist nur eine funktion die den internen wert zurückgibt, also nur ein "return variable;" beinhaltet. Set funktionen sind funktionen die den neuen variablenwert als argument entgegennehmen und ihn zuweisen.). Das ist sehr sicher, da du damit auch gleich unterbunden hast, dass die abgeleitete Klasse in den Daten der basisklasse rumpfuschen kann.

    ps: du solltest statt "zugriff auf den ostream der geerbten Klasse" lieber "zugriff auf die Ausgabefunktion (bzw operator<<) der geerbten Klasse" schreiben. Der ostream ist ein eigenes Objekt, dass keiner der beiden Klassen gehört 😉



  • Mit den get und set Funktionen meinst du einfach Funktionen mit denen ich einen Wert hinein bzw. heraushole in dieser Art:

    int getAnzahl(void)
    {
     return Anzahl;
    }
    

    Ich habe meine Vectoren immer noch im public Teil untergebracht, da ich eigentlich die Funktionalität der Vectoren nutzen wollte in dem ich einfach die PushBack Funktion ausführe.

    Bsp:

    Te.Daten.push_back(int);
    

    Sobald sie private sind, muß ich dafür gleich einige Funktionen schreiben, die das umkopieren, rausholen, sortieren...

    Ich habe in der Zwischenzeit, bis du mir das virtual gezeigt hast, auch etwas anderes ausprobiert.

    In den Klassen habe ich folgende Funktionen angelegt:

    // Daten speichern
             void speichern(ostream& out);
    
             // Daten laden
             void laden(istream& in);
    

    Und in der cpp

    void C1UOFreq::speichern(ostream& out)
    {
     // Daten an Ostream übergeben
     Eigenschaft.speichern(out);
     Freq.speichern(out);
     Container[0].speichern(out);
    }
    
    void C1UOFreq::laden(istream& in)
    {
     // Daten an Ostream übergeben
     Eigenschaft.laden(in);
     Freq.laden(in);
     Container[0].laden(in);
    }
    //---------------------------------------------------------------------------
    

    So übergebe ich den ostream, bzw. den istream in die jeweilige Klasse und jede Klasse setzt dann einfach ihre Daten in den Stream bzw. holt sie sich.

    void CElement::speichern(ostream& out)
    {
     // Daten an Ostream übergeben
     out << name << " " << masseinheit << " " << pegelmasseinheit << endl;
    }
    

    Weiß jetzt nicht ob das eine saubere Lösung ist. Deine sieht eleganter aus. Aber ich bin froh, dass ich das hinbekommen habe, bis du mir den Tip mit virtual gegeben hast.

    Wir hatten 3 Jahre lang C Unterricht und das höchste der Gefühle waren Strukturen. Im Vergleich zu dem was ich hier in diesem Forum in einem halben Jahr gelernt habe war das in der Schule Kinderkram.

    Danke nochmal für eure Hilfe.

    Viele Grüße und Frohe Ostern
    egcactus


Anmelden zum Antworten