Klassenentwurf
-
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; }
-
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(); }