Klassenentwurf



  • 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;
    }
    


  • Hallo

    Grundsätzlich können auch Vektoren als normale Variablen übergeben werden. Dein Beispiel scheint also richtig zu sein. Ich empfehle dir aber, vectoren oder andere Container, die nicht lokal deklariert sind, immer als (konstante) Referenzen zu übergeben. Damit kann der Compiler verhindern, das unnötig kopiert wird.

    class TfrmImport
    {
      ...
      const std::vector<CDataset>& GetVDataset();
      CDataset VDataset;
    };
    
    const std::vector<CDataset>& TfrmImport::GetVDataset()
    {
        return VDataset;
    }
    

    Sollte VDataset doch nur eine im Getter lokale Variable sein, dann mußt du es wie bei dir als Kopie übergeben.

    bis bald
    akari



  • Nun habe ich in meiner Hauptform (frmMain) den Vektor als private-Member deklariert und die getMethode GetVDataset() geschrieben. Da die Daten in einer anderen Form (hier frmNewDataset) importiert werden muss ich in dieser Form über die get-Methode auf den vektor zugreifen. Hier mal das Ergebnis:

    void __fastcall TfrmNewDataset::btnCreateNewDatasetClick(TObject *Sender)
    {
       TStringList* stlPaths = new TStringList();
    
       try {
          //alle markierten Dateien in Stringliste schreiben:
          for (int i=0; i<lviewData->Items->Count; i++) {
             if (lviewData->Items->Item[i]->Checked)
                stlPaths->Add(lviewData->Items->Item[i]->Caption +  lviewData->Items->Item[i]->SubItems->Strings[0]); //hier werden die Pfade in die Stringliste geschrieben
          }
       }
       __finally {
          delete stlPaths;
       }
       //neuen Datensatz anlegen:
       frmMain->GetVDataset().back().ImportData(stlPaths);
       frmMain->GetVDataset().push_back(CDataset());
    
       Close();
    }
    

    Soweit ich das verstanden habe wird dadurch der Konstruktor der Klasse CDataset aufgerufen und das Objekt in den Vektor geschoben. Nun möchte ich auf die Methode ImportData zugreifen um VData zu füllen. Das muss ich ja vor dem push_back machen oder? Also muss ich ja auf das letzte Element mit GetVDataset().back() zugreifen. Kann ich das so machen?
    Leider funktioniert bei solchen Sachen die Code-Vervollständigung vom Builder nicht.

    Die Methode GetVDataset konnt ich nicht als const deklarieren. Dann gibt es die Warnung, das push_back keine const-Funktion ist.



  • Hallo

    Ja, das const must du in deinem jetzt geschildertem Fall weglassen. Du brauchst eine Referenz die du bearbeiten kannst. Sei aber froh das ich dich generell auf die Referenz hingewiesen habe, denn mit deinem vorherigen Kopier-Getter würdest du auch nicht weit kommen...

    Zum Problem : Wenn schon, dann must du natürlich zuerst das Dataset anlegen und danach die Import-Funktion anwenden :

    frmMain->GetVDataset().push_back(CDataset()); // Neues Dataset am Ende anfügen
       frmMain->GetVDataset().back().ImportData(stlPaths); // Dataset am Ende mit Daten füllen
    

    bis bald
    akari



  • Nebenbei bemerkt, ich würde diesen try _finally Kram hier eher nicht machen.
    Ein try ist hier nicht nötig da bei korrekter Anwendung der StringList keine exception fliegen kann.
    Ich würde die StringList eher in einen auto_ptr packen. Der kümmert sich dann um das löschen.

    void __fastcall TfrmNewDataset::btnCreateNewDatasetClick(TObject *Sender)
    {
       std::auto_ptr<TStringList> stlPaths(new TStringList);
    
       //alle markierten Dateien in Stringliste schreiben:
       for (int i=0; i<lviewData->Items->Count; i++) {
          TListItem* item = lviewData->Items->Item[i]; // nur zum Sparen von Schreibarbeit
          if (item->Checked)
             stlPaths->Add(item->Caption +  item->SubItems->Strings[0]); //hier werden die Pfade in die Stringliste geschrieben
       }
       //neuen Datensatz anlegen:
       frmMain->GetVDataset().back().ImportData(stlPaths);
       frmMain->GetVDataset().push_back(CDataset());
    
       Close();
    }
    


  • ich habe Deinen Tipp berücksichtigt und das Programm umgeschrieben. Leider erhalte ich dann folgende
    Fehler in dieser Zeile:

    frmMain->GetVDataset().back().ImportData(stlPaths);
    

    Fehlermeldungen:

    [C++ Fehler] unitNewDataset.cpp(234): E2034 Konvertierung von 'std::auto_ptr<TStringList> *' nach 'TStringList *' nicht möglich
    [C++ Fehler] unitNewDataset.cpp(234): E2342 Keine Übereinstimmung des Typs beim Parameter 'stlPaths_' ('TStringList *' erwartet, 'std::auto_ptr<TStringList> *' erhalten)
    

    Dann kann ich den auto-ptr auch in dieser methode umschreiben?

    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]);
    
          try {
             for (int j=0; i<stlData->Count; i++) {
                TDateTime temp_timestamp;
                double temp_value;
                SplitString(stlData->Strings[j], &temp_timestamp, &temp_value);
                VData[i][j].value = temp_value;
                VData[i][j].timestamp = temp_timestamp;
                VData.push_back(std::vector<SValue>()); // erste Ebene füllen
                VData[j].push_back(SValue()); // zweite Ebene füllen
             }
          }
          __finally {
             delete stlData;
          }
       }
    }
    

    das mit dem try finally hatte ich in einem Artikel zu TStringList gelesen: http://www.fachinformatiker-ihk.de/download/extras/Fh_mit_BCB.pdf



  • Hallo

    rudpower schrieb:

    ich habe Deinen Tipp berücksichtigt und das Programm umgeschrieben. Leider erhalte ich dann folgende
    Fehler in dieser Zeile:

    frmMain->GetVDataset().back().ImportData(stlPaths);
    

    Las den auto_ptr noch seinen Inhalt als normalen Zeiger zurückgeben

    frmMain->GetVDataset().back().ImportData(stlPaths.get());
    

    Dann kann ich den auto-ptr auch in dieser methode umschreibe
    n?

    Ja, auch dafür kannst du auto_ptr benutzen und __finally entfernen.
    Überhaupt kannst du auto_ptr immer dann benutzen, wenn du eine rein lokale Instanz hast, die du eigentlich statisch auf dem Stack anlegen würdest (ohne new), aber es wegen der VCL-Konventionen nicht machen kannst.

    das mit dem try finally hatte ich in einem Artikel zu TStringList gelesen

    Grundsätzlich ist __finally ja eine nette Erweiterung im Builder - für C. In C++ sollte man lieber RAII benutzen.

    bis bald
    akari



  • akari schrieb:

    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

    Oh Pardon, hab ich glatt übersehen 😉



  • ok danke für den Tipp.
    Die Kompilierung läuft durch. Nachdem ich meine Dateien ausgewählt habe und den Datensatz anlege bricht das Programm mit einer EAccessViolation ab. Wenn ich den Haltepunkt in die ImportData-Methode setze kommt der Programmabbruch in der Zeile "VData[i][j].value = temp_value;"

    void CDataset::ImportData(TStringList* stlPaths_)
    {
       for (int i=0; i<stlPaths_->Count; i++) { //hier war noch ein Fehler, da stand vorher stlPaths_->Capicity
          std::auto_ptr<TStringList> stlData(new TStringList);
          stlData->LoadFromFile(stlPaths_->Strings[i]);
    
          for (int j=0; i<stlData->Count; i++) {
             TDateTime temp_timestamp;
             double temp_value;
             SplitString(stlData->Strings[j], &temp_timestamp, &temp_value);
             VData[i][j].value = temp_value; //hier bricht das Programm ab!
             VData[i][j].timestamp = temp_timestamp;
             VData.push_back(std::vector<SValue>()); // erste Ebene füllen
             VData[j].push_back(SValue()); // zweite Ebene füllen
          }
       }
    }
    


  • Hallo

    Ich habe nicht den Eindruck, das du überhaupt verstehst, was du programmierst...
    Denn sonst würde dir auffallen, das du zuerst Daten in den vector schiebst, und erst danach den Platz im vector schaffst, wo die Daten eigentlich hinsollen.

    bis bald
    akari



  • Ich stimme akari voll zu und würde dir (rudpower) raten dich mal über vector etwas gründlicher schlau zu machen.
    In guter Einstieg dafür bietet das hiesige Magazin. Z. Bsp. hier
    http://www.c-plusplus.net/forum/143816
    In diesen Fall hier könnte man auf push_back sogar ganz verzichten und std::vector::resize bemühen. Immerhin steht die Größe des vectors ja schon vorher fest.



  • so, nun hab ich mich nochmal genau mit vector beschäftigt. War klar, dass das nicht funktioniert wenn ich auf Elemente zugreife, die noch nicht reserviert wurden. Hier nun der funktionierende Code:

    void CDataset::ImportData(TStringList* stlPaths_)
    {
       VData.resize(stlPaths_->Count);
       for (int i=0; i<stlPaths_->Count; i++) { //durchläuft alle Dateien in der Stringliste
          std::auto_ptr<TStringList> stlData(new TStringList);
          stlData->LoadFromFile(stlPaths_->Strings[i]);
    
          for (int j=0; j<stlData->Count; j++) {
             TDateTime temp_timestamp;
             double temp_value;
             SplitString(stlData->Strings[j], &temp_timestamp, &temp_value);
    
             VData[i].resize(stlData->Count);
    
             VData[i][j].value = temp_value;
             VData[i][j].timestamp = temp_timestamp;
          }
       }
    }
    

    Vielen Dank nochmal für die Hilfestellungen und ein schönes Wochenende.



  • Da ist noch ein Fehler drin.
    Das zweite resize muss eine Schleife nach oben verschoben werden sonst machst du es viel zu oft.



  • Burkhi schrieb:

    akari schrieb:

    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

    Oh Pardon, hab ich glatt übersehen 😉

    Sorry für den Spam, aber ich bitte Burkhi mich über Private Nachricht anzuschreiben, ist wichtig. Oder geht das in diesem Forum nicht?



  • Hallo

    Reptilex schrieb:

    Sorry für den Spam, aber ich bitte Burkhi mich über Private Nachricht anzuschreiben, ist wichtig. Oder geht das in diesem Forum nicht?

    Die privaten Nachrichten sind in diesem Forum deaktiviert.
    Burkhi hat in seinem Profil auch keine E-Mail Adresse hinterlegt. Also kannst du Burkhi bitten, das er dir eine E-Mail schickt, an die Adresse in deinem Profil.

    bis bald
    akari



  • akari schrieb:

    Hallo

    Reptilex schrieb:

    Sorry für den Spam, aber ich bitte Burkhi mich über Private Nachricht anzuschreiben, ist wichtig. Oder geht das in diesem Forum nicht?

    Die privaten Nachrichten sind in diesem Forum deaktiviert.
    Burkhi hat in seinem Profil auch keine E-Mail Adresse hinterlegt. Also kannst du Burkhi bitten, das er dir eine E-Mail schickt, an die Adresse in deinem Profil.

    bis bald
    akari

    Hallo,

    okay dann mach ich das hiermit:

    Burkhi, bitte schreibe mir eine Mail an meine E-Mail damit ich dir dort schreiben kann!

    Schade, dass man ihn nicht leichter kontaktieren kann 😞


Anmelden zum Antworten