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