Klassenentwurf



  • Ich habe eine Fülle von Messdaten die als txt-Datei gespeichert sind. Die Dateien sind 2-spaltig aufgebaut wobei in der 1. Spalte immer die Zeit und in der 2. Spalte der Messwert steht. Die Spalten sind durch einen Tabulator getrennt. Die Bezeichnung des Messwerts steht im Dateinamen. Aufgabe der Software ist die Daten später grafisch darzustellen und auszuwerten. Parallel dazu gibt es viele andere Variablen, die aus den Messwerten berechnet werden. Da diese in verschiede Kategorien gehören, habe ich sie in unterschiedliche Klassen gelegt. Hier mal beispielhaft:

    class CKategorie1 {
        AnsiString var1;
        double var2;
        //usw...
    };
    class CKategorie1 {
        double varA;
        double varB;
        //usw. (sind eigentlich alles double)
    };
    

    Ich hatte mir gedacht, dass der Benutzer einen Datensatz anlegt, eine Bezeichnung eingibt (und evtl. weitere Angaben) und die benötigten Dateien importiert. Jeder Messwert ist dann ein Objekt der Klasse CValue:

    class CValue {
        TDateTime timestamp;
        AnsiString unit;
        double value;
    };
    

    Diese werden als Vektor vValues im Datensatz abgelegt:

    class CDataset {
        static int identifier;
        AnsiString name;
        vector<CValue>vValues;
    public:
        CDataset() {identifier++;}
    };
    int CDataset::identifier = 0;
    

    Hier fehlen natürlich noch die Get und Set-Methoden, da die Member alle private sind.
    Was meint ihr dazu?

    Wie mach ich die Deklaration/Definition der Objekte von Dataset? Sollte doch dynmisch sein oder? Kann ich die in private der Form deklarieren also:

    class TfrmMain : public TForm
    {
    __published:	
       TButton *btnCreateNewDataset;
    private:	// Anwender-Deklarationen
       CDataset* dataset;
       std::vector<CDataset>vDataset;
    public:		// Anwender-Deklarationen
       __fastcall TfrmMain(TComponent* Owner);
    };
    

    In der Form würde ich dann in einem Button den Datensatz erzeugen:

    void __fastcall TfrmMain::btnCreateNewDatasetClick(TObject *Sender)
    {
       dataset = new CDataset();
    }
    

    Da es ja mehrere Datensätze geben soll würde ich die Datensätze dann wieder in einen Vektor speichern.



  • Hallo,

    Belass es einfach bei der Variante mit dem vector<CDataset>. Was anderes brauchst du doch eigentlich nicht. Du kannst doch dann in deiner btnCreateNewDatasetClick Funktion einfach mit push_back einen neuen Datensatz zufügen.
    Wozu noch das hier CDataset* dataset;?



  • aber wie geb ich den Datenelementen name und vValues von CDataset Werte? Ich hätte gedacht, dass ich mit "CDataset* dataset = new CDataset" bei jedem Klick des Buttons ein neues Objekt erzeuge (dynamisch) und den Vektor danach mit vDataset.push_back(dataset) fülle.



  • Hallo

    Du hast das Prinzip der Speicherverwaltung in C++ noch nicht verstanden...
    Für das was du uns hier gezeigt hast, brauchst du weder manuelle dynamische Instanzen mit new noch einen extra Zeiger als Member.

    class TfrmMain : public TForm
    {
    ...
    private:    // Anwender-Deklarationen
       std::vector<CDataset>vDataset; // vector reicht
    };
    ...
    vDataset.push_back(CDataset()); // einfügen
    

    bis bald
    akari



  • Das macht aber keinen Sinn. Mit

    vDataset.push_back(CDataset());
    

    kannst du doch ganz leicht ein neues und leeres Element anfügen.
    Mit vDataset.back() hast du Zugriff auf das letzte Element des vectors. Damit kannst du dann alles machen was du auch mit einer normalen Variablen machen kannst.
    Außerdem brauchst du dich nicht um die Speicherverwaltung zu kümmern.
    Vielleicht beschreibst du mal warum du denkst so vorgehen zu müssen wie du es gerade willst.



  • ah ok. Das wusst ich nicht. Ich hab gedacht wenn ich einen Vektor mit Klassen verwende benötige ich zwingend ein Objekt der Klasse.



  • Hallo

    Selbstverständlich verwendet ein std::vector auch Objekte (korrekter : Instanzen) deiner Klasse. Nur eben übernimmt vector die gesamte Speicherverwaltung dafür selbst, deshalb must du nur ein statisches und keines dynamisches Objekt "pushen".

    Etwas anderes wäre es, wenn du deinen vector Zeiger verwalten lassen würdest :

    std::vector<CDataset*>vDataset;
    

    bis bald
    akari



  • Vielleicht beschreibst du mal warum du denkst so vorgehen zu müssen wie du es gerade willst.

    Das hat keinen direkten Grund. Mir ist nur keine Alternative eingefallen. Mein Ziel ist ja das der Benutzer zu Beginn einen neuen Datensatz anlegt, dann bestimmte Eingaben macht wie Bezeichnung, usw. und die Daten importiert (das sind die txt-Files). Der Datensatz soll dann in einer Listview angezeigt werden. Man soll die Daten aber auch ändern können bzw weitere Daten dazuladen können. Wenn man den Datensatz dann läd soll der der Benutzer die Messwerte grafisch anzeigen lassen können. Außerdem werden aus den Daten weitere Kennwerte berechnet (Siehe 1. Beitrag)



  • Du "verwaltest" in deiner Klasse CValue, wenn ich das richtig sehe, nur Variablen.
    Hier wäre ich nie auf die Idee gekommen dies in einer Klasse zu tun, sondern hätte eine Struktur bzw einen neuen Datentyp (typedef) angelegt, welcher anschließend in dem Vector gespeichert wird.
    Gibt es hier Vor- bzw Nachteile einer Klasse im Gegensatz zu einer Struktur?

    MfG Stephan



  • Hallo

    In C++ sind class und struct das gleiche, bis auf die Standard-Sichtbarkeit (class : private, struct : public). Und typedef hat damit gar nichts zu tun.

    bis bald
    akari



  • akari schrieb:

    Etwas anderes wäre es, wenn du deinen vector Zeiger verwalten lassen würdest :

    std::vector<CDataset*>vDataset;
    

    und das sollte man lassen sofern kein zwingender Grund dafür vorliegt (Stichwort Polymorphie). Zudem man hier auch auf eine korrekte Speicherfreigabe achten muss.
    Sowas können einem die boost-Pointercontainer abnehmen.



  • wie ich in zahlreicher Literatur lesen konnte, sollte man C (struct) und C++ (class) nicht mischen.



  • Hallo

    Das ist genauso Halbwissen. Wie ich schon sagte, gibt es in C++ fast keinen Unterschied zwischen struct und class. Der einzige Grund, die Verwendung von struct in C++ für sich selbst auszuschließen, ist eine selbstgewählte feste Systematik. Dem Compiler ist es egal.

    bis bald
    akari



  • rudpower schrieb:

    wie ich in zahlreicher Literatur lesen konnte, sollte man C (struct) und C++ (class) nicht mischen.

    Ich bin zwar auch ein Anhänger davon das man C-Spezifisches nicht mit C++-Spezifischen mischt, das hat aber nichts mit class und struct, sondern mit den unterschiedlichen Programmierparadigmen und den daraus resultierenden unterschiedlichen Ansätzen bei Bibliotheken etc. zu tun.



  • Noch mal zur Bestärkung.
    Selbst in der Standardbibliothek werden für bestimmte Zwecke weiterhin structs verwendet (siehe unary_function etc.).
    Für so etwas wie einen reinen Datencontainer ohne viel Funktionalität würde ich auch weiterhin structs nehmen.



  • alles klar, dann nehm ich für CValue ein struct. Ist eh einfacher, da ich keine Schnittstellen definieren muss. Ich habe mal meine Klasse CDataset angepasst und stoße nun auf ein Problem. Hier erst mal der Code:

    class CDataset {
       private:
          struct SValue {
             TDateTime timestamp;
             AnsiString unit;
             double value;
          };
          static int count; //zählt angelegten Datensätze
          AnsiString identifier;
          std::vector<std::vector<SValue> >VData; //2 dimensionaler Vektor
          void SplitString(AnsiString s_, TDateTime* timestamp_, double* value_);
       public:
          CDataset() {count++;}
          int GetCount() {return count;}
          AnsiString GetIdentifier() {return identifier;}
          void ImportData(TStringList* stlPaths_);
    };
    
    /---------------------------------------------------------------------------
    int CDataset::count = 0;
    //---------------------------------------------------------------------------
    void CDataset::ImportData(TStringList* stlPaths_)
    {
       for (int i=0; i<stlPaths_->Capacity; i++) { //durchläuft alle Dateien in der Stringliste
          TStringList* stlData = new TStringList();
          stlData->LoadFromFile(stlPaths_->Strings[i]); //läd alle Zeilen der aktuellen Datei in Stringliste stlData
    
          try {
             for (int j=0; i<stlData->Count; i++) { //durchläuft alle Zeilen der Stringliste stlData
                TDateTime temp_timestamp;
                double temp_value;
                SplitString(stlData->Strings[j], &temp_timestamp, &temp_value);
                VData[i][j] // --> hier komm ich nicht weiter!!!
             }
          }
          __finally {
             delete stlData;
          }
       }
    }
    //--------------------------------------------------------------------------
    //extrahiert Zeitwert und Messwert aus String:
    void CDataset::SplitString(AnsiString s_, TDateTime* timestamp_, double* value_)
    {
       int laenge = s_.Length();
       int posTab = s_.Pos("\t");
    
       *value_ = s_.SubString(posTab+1, laenge-posTab).ToDouble();
       *timestamp_ = s_.SubString(1, posTab-1).ToDouble();
    }
    

    Zur Erklärung:
    Die Methode ImportData enhält als Parameter eine Stringliste mit den vom Benutzer ausgewählten Dateien. Die werden durchlaufen. Der Inhalt jeder Datei wird in eine 2. Stringliste gespeichert die ebenfalls durchlaufen wird. Innerhalb dieser Schleife wird in jeder Zeile durch die Funktion SplitString der Zeit- und Messwert extrahiert und in die beiden Variablen geschrieben.
    Nun zum Problem. Da ich nicht weiss wieviele Dateien ausgewählt werden verwende ich einen Vektor. Der Vektor umfasst einen weiteren Vektor für die Messwerte. Wie kann ich nun die beiden Messwerte zuweisen und den Vektor füllen? Ich muss ja an die variablen der Struktur ran.
    Ich hoff, dass es einigermaßen verständlich ist und dass ich auf dem richtigen Weg bin.



  • Hallo

    Wo ist dein Problem?

    VData[i][j].value = 0;
    // bzw. das füllen
    VData.push_back(std::vector<SValue>()); // erste Ebene füllen
    VData[0].push_back(SValue()); // zweite Ebene füllen
    

    bis bald
    akari



  • akari schrieb:

    Hallo

    Das ist genauso Halbwissen. Wie ich schon sagte, gibt es in C++ fast keinen Unterschied zwischen struct und class. Der einzige Grund, die Verwendung von struct in C++ für sich selbst auszuschließen, ist eine selbstgewählte feste Systematik. Dem Compiler ist es egal.

    bis bald
    akari

    ..noch ergänzend dazu:
    In structs sind alle Members standardmässig public, in classes private.



  • Hallo

    Burkhi schrieb:

    ..noch ergänzend dazu:
    In structs sind alle Members standardmässig public, in classes private.

    Das habe ich in meinem Post auf Seite 1 schon geschrieben, das von dir zitierte ist nur die Wiederholung.

    bis bald
    akari



  • ok. Alles klar.
    Den Vektor std::vector<CDataset>VDataset; habe als private-Member in der Form deklariert. Wenn ich aus einer anderen Form darauf zugreifen will muss ich ja eine Schnittstelle definieren über eine Get-Methode. Wie macht man das bei Vektoren? Oder kann ich das einfach als public machen? Ich habs es mal wie bei elementaren Datentypen gemacht:

    std::vector<CDataset> TfrmImport::GetVDataset()
    {
        return VDataset;
    }
    

Anmelden zum Antworten