Fragen zu TList und ListView



  • Hallo zusammen,

    ich habe ein paar Fragen zu dem folgenden Beispiel aus dem Forum:

    //---------------------------------------------------------------------------
    #include <vcl.h>
    #pragma hdrstop
    
    #include "Unit2.h"
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    //---------------------------------------------------------------------------
    #define ANZAHLDATEN 30000 
    
    TForm2 *Form2;
    TList *MeineDaten = 0;       
    
    struct Daten
    { 
      String vorname; 
      String nachname; 
    
      Daten(String Vorname, String Nachname)
      { 
       vorname = Vorname; 
       nachname = Nachname; 
      } 
    }; 
    //--------------------------------------------------------------------------- 
    String ZufallsString() 
    { 
     int length = random(10) + 3; 
     String zstring = ""; 
     char chars [] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 
     for (int i=0;i<length;i++) 
     { 
      zstring += chars[ random( strlen(chars) ) ]; 
     } 
     return zstring; 
    } 
    //--------------------------------------------------------------------------- 
    __fastcall TForm2::TForm2(TComponent* Owner)        : TForm(Owner){}
    //--------------------------------------------------------------------------- 
    void __fastcall TForm2::FormCreate(TObject *Sender)
    { 
     randomize();
     //Hier sammeln wir unsere Daten drin 
     MeineDaten = new TList(); 
     //OwnerData auf true setzen, sonst geht das Beispiel nicht 
     ListView1->OwnerData = true; 
     ListView1->RowSelect = true; 
     ListView1->ViewStyle = vsReport; 
     //Zwei Spalten einfügen 
     TListColumn *column = ListView1->Columns->Add(); 
     column->Caption = "Index"; 
     column = ListView1->Columns->Add(); 
     column->Caption = "Spalte1";
     column = ListView1->Columns->Add();
     column->Caption = "Spalte2";
    
     //Dummydaten erzeugen
     for(int i=0;i<ANZAHLDATEN;i++)
     {
       MeineDaten->Add( new Daten( ZufallsString() , ZufallsString() ) );
     }
    
     //ListView-Inhalt anzeigen
     FuelleView();
    }
    //--------------------------------------------------------------------------- 
    void __fastcall TForm2::ListView1Data(TObject *Sender, TListItem *Item)
    {
     if ((Item->Index < MeineDaten->Count)) 
     { 
      Daten *d =(Daten*)MeineDaten->Items[Item->Index]; 
      Item->Caption = IntToStr(Item->Index); 
      Item->SubItems->Add(d->vorname);
      Item->SubItems->Add(d->nachname); 
     } 
    } 
    //---------------------------------------------------------------------------
    void TForm2::FuelleView()
    {
      ListView1->Items->Count = MeineDaten->Count; 
      ListView1->Items->BeginUpdate(); 
      try 
      { 
       if (ListView1->Items->Count > 0) 
       { 
        //Erste Element markieren 
        ListView1->Selected = ListView1->Items->Item[0]; 
        ListView1->Selected->Focused = true; 
        ListView1->Selected->MakeVisible(false); 
       } 
      } 
      __finally 
      { 
       ListView1->Items->EndUpdate(); 
      } 
    }
    //---------------------------------------------------------------------------
    
    void __fastcall TForm2::Button2Click(TObject *Sender)
    {
     if ( MeineDaten )
       {
    	delete MeineDaten;
       }
     Close();	
    }
    //---------------------------------------------------------------------------
    
    void __fastcall TForm2::Button1Click(TObject *Sender)
    {
     //Weitere Dummydaten erzeugen
     for(int i=0;i<ANZAHLDATEN;i++)
     {
       MeineDaten->Add( new Daten( ZufallsString() , ZufallsString() ) );
     }
    
     //ListView-Inhalt anzeigen
     FuelleView();
    }
    //---------------------------------------------------------------------------
    

    Kann mir bitte jemand erklären, wie das hier funktioniert:

    //Dummydaten erzeugen
     for(int i=0;i<ANZAHLDATEN;i++)
     {
       MeineDaten->Add( new Daten( ZufallsString() , ZufallsString() ) );
     }
    

    Woher kennt denn in Funktion "FuelleView()" der Aufruf "ListView1->Items->EndUpdate()" vorname und nachnahme aus der struct "Daten" ?

    Was passiert hier genau, wenn der Name der struct und der Name der Funktion gleich sind. Muss da immer über new ein neues Objekt erzeugt werden ? Warum funktioniert das hier nicht, wenn die Namen unterschiedlich sind ?

    struct Namen
    { 
      String vorname; 
      String nachname; 
    
      Daten(String Vorname, String Nachname)
      { 
       vorname = Vorname; 
       nachname = Nachname; 
      } 
    }; 
    
     //Dummydaten erzeugen
     for(int i=0;i<ANZAHLDATEN;i++)
     {
       MeineDaten->Add( new Namen->Daten( ZufallsString() , ZufallsString() ) );
     }
    

    Vielen Dank für eure Hilfe im voraus.



  • Hallo

    Woher kennt denn in Funktion "FuelleView()" der Aufruf "ListView1->Items->EndUpdate()" vorname und nachnahme aus der struct "Daten" ?

    Weil der globale Zeiger MeineDaten zum Datenaustausche zwischen den Funktionen benutzt wird. Im Konstruktor von TForm2 wird dieser Zeiger mit einer Instanz von TList versehen. Danach werden diese Liste mehrere Instanzen von Daten eingefügt. Daraufhin können alle folgenden Methoden auf diese Liste zugreifen. Um dieses Verhalten nachzuvollziehen solltest du dich mit den Stichworten Gültigkeitsbereiche/Lebenszeit von Instanzen beschäftigen.

    Was passiert hier genau, wenn der Name der struct und der Name der Funktion gleich sind. Muss da immer über new ein neues Objekt erzeugt werden ? Warum funktioniert das hier nicht, wenn die Namen unterschiedlich sind ?

    Methoden deren Name mit ihrer Klasse übereinstimmen sind sogenannte Konstruktoren. Sie erlauben eine vereinfachte und unter umständen auch beschleunigte Erstellung von Instanzen dieser Klasse, und auch weitere Funktionalität. Ob Instanzen mit new oder ohne angelegt werden ist dabei egal, auch statische Instanzen können mit Konstruktoren erstellt werden. Weiteres dazu (immerhin die Grundlagen von OOP) findest du in Tutorials wie diesem hier.

    Übrigens ist dieses Beispiel keineswegs optimal, es beinhaltet mehrere kleinere und auch größere Schwächen. Um den Umgang mit TListView zu verstehen ist es ausreichend, aber es ist kein Vorbild in Bezug auf sicheren und eleganten Einsatz von dynamischer Speicherverwaltung oder Ausnutzung der Möglichkeiten von OOP in C++.

    bis bald
    akari



  • Du solltest dir unbedingt mal die grundlegenden C++ Sprachelemente anschauen, insbesondere die Verwendung von Klassen und dynamischer Speicherverwaltung per new/delete.

    In C++ ist die Funtkion der Schlüsselwörter struct und class fast identisch. Mit diesen Schlüsselwörtern definierst du dir eigene Datentypen, die abhängig von ihrer Definition bestimmte Eigenschaften besitzen. In deinem Beispiel wird mit

    struct Daten
    { 
      String vorname; 
      String nachname; 
    
      Daten(String Vorname, String Nachname)
      { 
       vorname = Vorname; 
       nachname = Nachname; 
      } 
    };
    

    ein neuer Datentyp Daten erzeugt, der die Elemente vorname und nachname besitzt. Der Datentyp Daten hat einen Konstruktor, über den neue Objekte erzeugt und mit den übergebenen Parametern initialisiert. Der/die Konstruktor(en) muss/müssen immer den gleichen Namen wie die Klasse haben, sie können aber unterschiedliche und/oder unterschiedlich viele Parameter besitzen.
    Dein Beispiel funktioniert nicht, da du den Klassennamen in Namen umbenannt hast.
    Im übrigen erzeugt das Programm ein Speicherleck, da es eine Anzahl von Daten Objekten dynamisch erzeugt und in eine TList einfügt, die dynamisch erzeugten Objtke aber niemals wieder freigibt. Zu jedem new Aufruf gehört ein delete Aufruf, es sei denn eine andere Funktion/Klasse übrnimmt den Besitz des Objektes und kümmert sich selbst um dessen Zerstörung.



  • DocShoe schrieb:

    Im übrigen erzeugt das Programm ein Speicherleck, da es eine Anzahl von Daten Objekten dynamisch erzeugt und in eine TList einfügt, die dynamisch erzeugten Objtke aber niemals wieder freigibt. Zu jedem new Aufruf gehört ein delete Aufruf, es sei denn eine andere Funktion/Klasse übrnimmt den Besitz des Objektes und kümmert sich selbst um dessen Zerstörung.

    akari schrieb:

    Übrigens ist dieses Beispiel keineswegs optimal, es beinhaltet mehrere kleinere und auch größere Schwächen. Um den Umgang mit TListView zu verstehen ist es ausreichend, aber es ist kein Vorbild in Bezug auf sicheren und eleganten Einsatz von dynamischer Speicherverwaltung oder Ausnutzung der Möglichkeiten von OOP in C++.

    bis bald
    akari

    Vielen Dank für eure Hilfe. Das mit dem Constructor ist mir jetzt wie Schuppen von den Augen gefallen, da hat mich nur das Wort struct geblendet. Ich dachte den Ctor gibt es nur bei Klassen.

    Aber das mit dem Speicherleck habe ich noch nicht verstanden. Beim Schließen von Form2 delete ich ja die TList ? Und wenn ich in der for-Schleife "Daten" deleten möchte, dann bekomme ich eine Fehlermeldung dass das nicht geht 😕
    Wie und wo kann ich denn die "new Daten" deleten ?



  • Hallo

    Aber das mit dem Speicherleck habe ich noch nicht verstanden. Beim Schließen von Form2 delete ich ja die TList

    Das Problem ist, das die TList-Instanz nicht die sogenannte "Ownership" der ihrer Elemente besitzt. Wenn du die TList-Instanz löscht, löscht TList nicht automatisch die Instanzen auf die die Pointer in der Liste zeigen. Das mußt du selber machen.

    Und wenn ich in der for-Schleife "Daten" deleten möchte, dann bekomme ich eine Fehlermeldung dass das nicht geht

    Das liegt daran das TList nur die void-Pointer kennt, aber nicht weiß was für Instanzen hinter diesen void-Pointern stehen. In C++ aber braucht der delete-Operator unbedingt die genaue Klasse der zu löschenden Instanz. Wenn du also, wie bereits im Beispiel gezeigt, den Zeiger auf den Typ castest, kannst du deine for-Schleife ausführen.

    for (...)
      delete ((Daten*)MeineDaten->Items[lv]);
    

    Um all diesen Aufwand zu umgehen, bietet sich an anstelle von TList* lieberdas standardkonforme und elegantere

    std::vector<Daten>
    

    zu verwenden. Damit ersparrt man sich
    - casten
    - dynamische Speicherverwaltung
    - manuelles Löschen

    Natürlich erfordert das auch eine entsprechende Einarbeitung in den C++ Standard.

    bis bald
    akari



  • akari schrieb:

    Hallo

    Aber das mit dem Speicherleck habe ich noch nicht verstanden. Beim Schließen von Form2 delete ich ja die TList

    Das Problem ist, das die TList-Instanz nicht die sogenannte "Ownership" der ihrer Elemente besitzt. Wenn du die TList-Instanz löscht, löscht TList nicht automatisch die Instanzen auf die die Pointer in der Liste zeigen. Das mußt du selber machen.

    Und wenn ich in der for-Schleife "Daten" deleten möchte, dann bekomme ich eine Fehlermeldung dass das nicht geht

    Das liegt daran das TList nur die void-Pointer kennt, aber nicht weiß was für Instanzen hinter diesen void-Pointern stehen. In C++ aber braucht der delete-Operator unbedingt die genaue Klasse der zu löschenden Instanz. Wenn du also, wie bereits im Beispiel gezeigt, den Zeiger auf den Typ castest, kannst du deine for-Schleife ausführen.

    for (...)
      delete ((Daten*)MeineDaten->Items[lv]);
    

    akari

    Nur um es abschließend verstanden zu haben, dann wäre das Speicherleck durch folgende Änderung beseitigt:

    void __fastcall TForm2::Button2Click(TObject *Sender)
    {
     for ( int i=0; i < MeineDaten->Count; i++ )
       {
    	delete ((Daten*)MeineDaten->Items[i]);
       }
    
     if ( MeineDaten )
       {
    	delete MeineDaten;
       }
     Close();
    }
    

    Ist das hier falsch in deinem Bezug auf "Ownership" ? Muss da in die Klammern noch etwas rein ?

    MeineDaten = new TList();
    

    Mit der Geschichte vector werde ich mich dann etwas näher befassen 🕶



  • Du solltest vor dem Zugriff auf MeineDaten den Zeiger prüfen. Ausserdem solltest du C++ casts benutzen statt C-style casts.

    void __fastcall TForm2::Button2Click(TObject *Sender)
    {
       if ( MeineDaten )
       {
          for ( int i=0; i < MeineDaten->Count; i++ )
          {
             delete reinterpret_cast<Daten*>( MeineDaten->Items[i] );
          }
          delete MeineDaten;
          MeineDaten = 0;
       }
       Close();
    }
    

    Du kannst für TList kein Owner angeben, der sich um die Zerstörung der Liste bzw. deren Inhalt kümmert. Hier ist wirklich Handarbeit gefragt.

    trial schrieb:

    Mit der Geschichte vector werde ich mich dann etwas näher befassen

    Löblich! Du wirst begeistert sein, was man mit der STL so alles anstellen kann und dich fragen, wie du bisher ohne ausgekommen bist.

    /Edit akari : Tags korrigiert



  • Was ist da jetzt schiefgegangen?



  • DocShoe schrieb:

    Was ist da jetzt schiefgegangen?

    [cpp][/cpp] erlaubt keine verschachtelten Tags.

    Wärest du registriert, könntest du es ausbessern 😉



  • audacia schrieb:

    DocShoe schrieb:

    Was ist da jetzt schiefgegangen?

    [cpp][/cpp] erlaubt keine verschachtelten Tags.

    Wärest du registriert, könntest du es ausbessern 😉

    Jaaaaaaa, ich weiß. Ich mich damals beim Registrieren wohl ausgesprochen blöde angestellt, jedenfalls kann ich mich weder mit meinem gewünschten Nick noch meiner Email Adresse registrieren. Hab´ dem Admin vorhin ´ne Mail geschickt, ob man das zurücksetzen kann, dann registriere ich mich auch. Versprochen!



  • Danke ! Da hab ich wieder einiges gelernt 🙂

    Eine Frage noch ...

    DocShoe schrieb:

    Du kannst für TList kein Owner angeben, der sich um die Zerstörung der Liste bzw. deren Inhalt kümmert. Hier ist wirklich Handarbeit gefragt.

    Wie erkennt man denn ob man einen Owner beim Erstellen mit angeben kann oder nicht ?



  • Hallo

    Ein Owner ist auch nur eine Eigenschaft. Ob eine Klasse also eine Owner-Eigenschaft hat und damit deren Konstruktor auch einen entsprechenden Parameter, kannst du in der Builder-Hilfe nachlesen.

    bis bald
    akari



  • Hallo zusammen,

    ich habe mal eine Version mit vector zusammengebastelt und möchte von euch mal hören ob das so ok ist ?

    //---------------------------------------------------------------------------
    #include <vcl.h>
    #include <iostream>
    #include <vector>
    #pragma hdrstop
    
    #include "Unit3.h"
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    //---------------------------------------------------------------------------
    using namespace std;
    
    const int ANZAHLDATEN = 30000;
    const int NoOfColums  = 3;
    TForm3 *Form3;
    vector<string> MeineVectorDaten;
    //---------------------------------------------------------------------------
    
    string zufallsString()
    { 
     int length = random(10) + 3; 
     string zstring = "";
     char chars [] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
     for ( int i = 0; i < length; i++ )
    	{
    	 zstring += chars[ random( strlen(chars) ) ];
    	}
     return zstring; 
    } 
    //---------------------------------------------------------------------------
    
    __fastcall TForm3::TForm3(TComponent* Owner)        : TForm(Owner){}
    //---------------------------------------------------------------------------
    
    void __fastcall TForm3::FormCreate(TObject *Sender)
    {
     randomize();
     //OwnerData auf true setzen, sonst geht das Beispiel nicht
     ListView1->OwnerData = true;
     ListView1->RowSelect = true; 
     ListView1->ViewStyle = vsReport; 
     //Drei Spalten einfügen
     TListColumn *column = ListView1->Columns->Add(); 
     column->Caption = "Index"; 
     column = ListView1->Columns->Add(); 
     column->Caption = "Spalte1";
     column = ListView1->Columns->Add();
     column->Caption = "Spalte2";
     column = ListView1->Columns->Add();
     column->Caption = "Spalte3";
    
     //Dummydaten erzeugen
     for ( int i = 0; i < ANZAHLDATEN; i++ )
    	{
    	 MeineVectorDaten.push_back( IntToStr( i ).c_str() );
    	 MeineVectorDaten.push_back( zufallsString() );
    	 MeineVectorDaten.push_back( zufallsString() );
    	}
    
     //ListView-Inhalt anzeigen
     FuelleView();
    }
    //---------------------------------------------------------------------------
    
    void __fastcall TForm3::ListView1Data(TObject *Sender, TListItem *Item)
    {
     vector<string>::iterator j;
    
     if ( Item->Index < MeineVectorDaten.size()/NoOfColums )
       {
    	j = MeineVectorDaten.begin() + (Item->Index * NoOfColums);
    	Item->Caption = IntToStr(Item->Index);
    	Item->SubItems->Add( static_cast<AnsiString>( (*j++).c_str() ) );
    	Item->SubItems->Add( static_cast<AnsiString>( (*j++).c_str() ) );
    	Item->SubItems->Add( static_cast<AnsiString>( (*j++).c_str() ) );
       }
    }
    //---------------------------------------------------------------------------
    
    void TForm3::FuelleView()
    {
      ListView1->Items->Count = MeineVectorDaten.size()/NoOfColums;
      ListView1->Items->BeginUpdate(); 
      try 
      { 
       if ( ListView1->Items->Count > 0 )
    	 {
    	  //Erste Element markieren
    	  ListView1->Selected = ListView1->Items->Item[0];
    	  ListView1->Selected->Focused = true;
    	  ListView1->Selected->MakeVisible(false);
    	 }
      }
      __finally
      { 
       ListView1->Items->EndUpdate(); 
      }
    }
    //---------------------------------------------------------------------------
    
    void __fastcall TForm3::Button1Click(TObject *Sender)
    {
     //Weitere Dummydaten erzeugen
     for ( int i = 0; i < ANZAHLDATEN; i++ )
    	{
    	 MeineVectorDaten.push_back( IntToStr( i ).c_str() );
    	 MeineVectorDaten.push_back( zufallsString() );
    	 MeineVectorDaten.push_back( zufallsString() );
    	}
    
     //ListView-Inhalt anzeigen
     FuelleView();
    }
    //---------------------------------------------------------------------------
    
    void __fastcall TForm3::Button2Click(TObject *Sender)
    {
     Close();
    }
    //---------------------------------------------------------------------------
    

    2 Fragen hab ich noch zusätzlich:

    Wie bekomme ich "vector<string> MeineVectorDaten;" aus dem globalen Bereich raus und kann ich anstatt einem string auch 3 strings mit einem push_back Aufruf übergeben ?



  • Hallo

    Wie bekomme ich "vector<string> MeineVectorDaten;" aus dem globalen Bereich raus

    Du kannst es als Member von TForm3 deklarieren. Ist auch wirklich besser als global. Dazu must du es nur in die Header-Datei verschieben :

    // TForm3.h
    ...
    #include <vector> // hinzufügen
    
    class TForm3 : public TForm
    {
      public :
    
      private :
        ...
        vector<string> MeineVectorDaten; // hinzufügen
    
    };
    

    kann ich anstatt einem string auch 3 strings mit einem push_back Aufruf übergeben

    Nein

    bis bald
    akari



  • Hallo akari,

    akari schrieb:

    Du kannst es als Member von TForm3 deklarieren. Ist auch wirklich besser als global. Dazu must du es nur in die Header-Datei verschieben :

    // TForm3.h
    ...
    #include <vector> // hinzufügen
    
    class TForm3 : public TForm
    {
      public :
    
      private :
        ...
        vector<string> MeineVectorDaten; // hinzufügen
        
    };
    

    Wenn ich das so in die Header-Datei übernehme, bekomme ich folgenden Fehler:

    25 ...	vector<string> MeineVectorDaten;
    
    [C++ Fehler] Unit3.h(25): E2303 Typname erwartet
    [C++ Fehler] Unit3.h(25): E2139 In Deklaration fehlt ;
    

    Wenn ich in der Header-Datei noch zusätzlich "using namespace std;" einfüge, dann compiliert er ohne Fehler, aber ich mal hier im Forum gelesen, dass man "using namespace std;" nicht in die Header-Datei einfügen soll, oder kann man das doch 😕



  • Hallo

    Ja das using namespace std sollte besser nicht in einem Header stehen (auch wenn das für dein Projekt wohl kein Einfluß hat).
    Ist auch kein Problem das using in der cpp-Datei zu lassen, du must im Header eben nur den Namespace bei vector und string explizit angeben

    std::vector<std::string> MeineVectorDaten;
    

    bis bald
    akari



  • Danke akari, damit funktioniert es einwandfrei !

    Eine Frage noch zu meinem Verständnis:

    // Das alleine funktioniert nicht
    #include <vector>
    ...
    vector<std::string> MeineVectorDaten;
    
    ...............
    // Das funktioniert auch nicht und der Compiler sagt vector ist kein Element von std
    //#include <vector>
    std::vector<std::string> MeineVectorDaten;
    

    Wenn vector kein Element von std ist, warum brauch ich dann std::vector ? 😕



  • Hallo

    std ist ein Namespace. std::vector ist also im Header <vector> für den Namespace std definiert. Du brauchst beide Verweise, um es verwenden zu können, egal ob du using verwendest oder "std::" ausschreibst.

    bis bald
    akari


Anmelden zum Antworten