Structs und listen



  • schönen guten Tag^^
    Ich habe schon rumgesucht, aber irgendwie nichts passendes gefunden. Vllt könnt ihr mir da weiterhelfen:

    Ich möchte Structs in einer Liste aufbewahren, die Liste dann ausgeben, bzw nur bestimmte Structs aus der Liste ausgeben.

    als Beispiel steht in einer Headerdatei

    struct SCity
    {
    std::string name;
    std::string state;
    int population = 0;
    }
    

    und dann noch eine zweite Headerdatei

    struct SDataBase
    {
    
    	int counter = 0;
    	int capacity = 0;
    
    	std::list<SCity> cityList;
    };
    

    in der passenden .cpp

    void Initialize(SDataBase* _pTownData)
    {
    	
    	SCity egTown;
    	egTown.name = "Erfurt";
    	egTown.state = "Thueringen";
    	egTown.population = 42;
    
    	_pTownData->cityList.push_back(egTown);
    	
    }
    

    Ja es gibt auch einen Header für SDatabase. Das soll jetzt nur als Beispiel dienen.

    Da ich mit listen noch gar nicht gearbeitet habe, sind jetzt hier meine Fragen:

    1. Funktioniert das in der cpp so? Wird dann egTown so in der List abgelegt?
    2. Wie kann ich jetzt sinnvoll die eingefügten Städte wieder ausgeben lassen?
    3. Wie kann ich die eingegeben Städte bearbeiten?

    Ich habe zu dem Thema wirklich nichts aufschlussreiches gefunden. Es geht immer nur um einfache Datentypen, oder die Antworten schweifen vom eigentlichen Thema ab.
    Es geht mir wirklich nur um das Thema Liste und nicht ob hier eine Klasse oder ähnliches sinnvoll wäre ^^ Ich habe eine Aufgabe gestellt bekommen und ich versuche da jetzt eine Lösung zu finden. OHNE Klassen^^

    Möglich wäre auch noch ein Array, aber das ist mir zu statisch. Bzw ich müsste für jede neue Stadt die eingegeben wird, das alte Array in das neue überführen, welches dann um eins größer ist und dann das alte Array löschen (deswegen ist der Struct von DataBase so angelegt). Und ich habe bei Arrays ein ähnliches Problem. Da ich es seltsam finde ein dynamisches Array vom Typ City zu erstellen, dann dem Struct DataBase einen Pointer auf das Array zu geben und so dann das Array zu füllen und zu bearbeiten.

    Kurz um Structs und Container sind mir irgendwie fremd 😃

    Ich hoffe ich finde hier eine brauchbare Antwort auf das kleine Problem.

    Ich danke schon mal für eure Antworten



  • @Avartos sagte in Structs und listen:

    Ich möchte Structs in einer Liste aufbewahren, die Liste dann ausgeben, bzw nur bestimmte Structs aus der Liste ausgeben.

    Standardfrage: warum list statt vector? (ja, list mag manchmal besser sein, aber per default würde ich immer vector nehmen)

    als Beispiel steht in einer Headerdatei

    struct SCity
    {
    std::string name;
    std::string state;
    int population = 0;
    }
    

    Soweit ok, verstanden.

    und dann noch eine zweite Headerdatei

    struct SDataBase
    {
    
    	int counter = 0;
    	int capacity = 0;
    
    	std::list<SCity> cityList;
    };
    

    Wozu soll diese Klasse nun gut sein? Eine std::list weiß doch, wie viele Elemente drin sind. Und was ist capacity? Das gibts bei einem vector, aber bei einer list?

    void Initialize(SDataBase* _pTownData)
    {
    	
    	SCity egTown;
    	egTown.name = "Erfurt";
    	egTown.state = "Thueringen";
    	egTown.population = 42;
    
    	_pTownData->cityList.push_back(egTown);
    	
    }
    

    Urgh. Variablen mit Unterschrich vorne sind bäh (ggf. sind sie reserviert, siehe z.B. hier für genaueres: https://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier - zumindest bei __ und _Grossbuchstabe, bei _klein kann ich mir die Regeln nicht merken (nur im globalen Namespace reserviert, wie ich gerade nachgelesen habe) und rate daher generell von _ vorne im Namen ab)

    Und warum der Umweg über die SDataBase-Klasse?

    Da ich mit listen noch gar nicht gearbeitet habe, sind jetzt hier meine Fragen:

    1. Funktioniert das in der cpp so? Wird dann egTown so in der List abgelegt?

    Ja, würde funktionieren (probiere es doch aus!)

    1. Wie kann ich jetzt sinnvoll die eingefügten Städte wieder ausgeben lassen?
    for (const auto &city : cityList) {
      std::cout << city.name << " in " << city.state << "\n";
      // oder den operator<< für SCity implementieren!
    }
    
    1. Wie kann ich die eingegeben Städte bearbeiten?

    Zum Beispiel Bevölkerungsverdopplung durchführen:

    auto faktor = 2;
    for (auto &city : cityList) {
      city.population *= faktor;
    }
    

    Möglich wäre auch noch ein Array, aber das ist mir zu statisch.

    Nimm std::vector! Ist wie ein Array, aber größenvariabel.

    Ansonsten ist mir nicht klar, wo genau du Probleme hast.



  • @Avartos sagte in Structs und listen:

    Funktioniert das in der cpp so? Wird dann egTown so in der List abgelegt?

    Ja, funktioniert, aber es wird eine Kopie in der Liste abgelegt.



  • @Avartos sagte in Structs und listen:

    Es geht immer nur um einfache Datentypen

    Das ist eigentlich egal. Ein int verhält sich in der Liste genauso wir deine struct.



  • Zusätzlich zu dem was andere schon gesagt haben ...

    @Avartos sagte in Structs und listen:

    void Initialize(SDataBase* _pTownData)
    

    Wenn ein Parameter nicht optional ist (also nicht nullptr sein kann), dann übergibt eine Referenz und keinen Pointer.



  • Danke für die Antworten 🙂
    Ich werde mich nachher mal daran setzen und es umsetzen.

    Der Umweg über SDataBase liegt an der AUfgabenstellung und das mit den Unterstrichen ist einfach ein Stil, den ich mir angewöhnen musste^^.
    ich nehme deshlab keinen Vector, weil ich dann auch mal an bestimmten Stellen der Liste etwas einfügen soll bzw entfernen muss.
    Das ist meines Wissens nach mit einem Vector nicht möglich.

    Mir ging es hier jetzt einfach um die Ausgabe der Inhalte aus der Liste, also:

    BeispielStadt1.Name
    BeispielStadt1.State
    BeispielStadt1.Population

    BeispielStadt2.Name
    BeispielStadt2.State
    BeispielStadt2.Population

    usw.
    und auch deren Bearbeitung an einer bestimmten Stelle.

    @manni66 sagte in Structs und listen:

    @Avartos sagte in Structs und listen:

    Funktioniert das in der cpp so? Wird dann egTown so in der List abgelegt?

    Ja, funktioniert, aber es wird eine Kopie in der Liste abgelegt.

    Um den Pointer zu übergeben wäre es dann ja

    _pTownData->cityList.push_back(&egTown);
    

    oder?

    LG
    Avartos



  • @Avartos sagte in Structs und listen:

    Um den Pointer zu übergeben wäre es dann ja
    _pTownData->cityList.push_back(&egTown);

    Deine List nimmt keine Pointer auf. Das ist auch gut so, denn du würdest einen Zeiger auf ein lokales Objekt einfügen, das kurz nach dem push_back auch schon nicht mehr existiert.



  • @Avartos sagte in Structs und listen:

    Der Umweg über SDataBase liegt an der AUfgabenstellung

    Und wozu sollen counter und capacity gut sein?

    und das mit den Unterstrichen ist einfach ein Stil, den ich mir angewöhnen musste^^.

    Sei dir dann aber bewusst, wann das reserviert ist und wann nicht (siehe Link oben).

    ich nehme deshlab keinen Vector, weil ich dann auch mal an bestimmten Stellen der Liste etwas einfügen soll bzw entfernen muss.
    Das ist meines Wissens nach mit einem Vector nicht möglich.

    Update dein Wissen hier: https://en.cppreference.com/w/cpp/container/vector/insert und https://en.cppreference.com/w/cpp/container/vector/erase

    Sicherlich - dann wird im Vector ggf. ein bisschen was rumkopiert. D.h. insert/erase ist nicht "umsonst" (es sei denn, es ist hinten und genug Kapazität da). Bei einer Liste ist ein Einfügen/Entfernen selbst aber auch nicht kostenlos, denn dafür muss erstmal die richtige Position gesucht werden. Das Iterieren einer std::list ist aber wesentlich langsamer als das bei einem vector. Daher ist vector häufig schneller, auch obwohl bei insert kopiert werden muss. Generelle Aussagen sind natürlich immer gefährlich, aber im Regelfall fährt man mit "vector nehmen" ganz gut. Ansonsten siehe hier für ein paar Diagramme, die zur Containerauswahl helfen sollen: https://stackoverflow.com/questions/471432/in-which-scenario-do-i-use-a-particular-stl-container/471461 - das ist für dein Beispielprogramm aber wahrscheinlich völlig egal.



  • Ich danke nochmals für die hilfreichen Antworten.
    Mit der Hilfe konnte ich das jetzt schnell lösen und mich anderen Sachen zuwenden.

    Ich habe es jetzt ganz einfach die Zeiger in die Liste übergeben und greife über die For-Schleife darauf zu.

    
    struct SCity
    {
    std::string m_name;
    }
    
    SCity* egTown = new SCity();
    
    egTown->name = "irgendwas"
    
    std::list<SCity*> cityList;
    
    for (SCity* value : cityList)
    {
    std::cout << value->m_name;
    } 
    
    

    Und danke auch für den Link mit den Reservierungen.

    LG
    Avartos



  • Ich nutze einfach nochmal hier das Thema.

    ich habe jetzt mal weiter an dem Code mich versucht und wollte jetzt die Elemente aus der Liste löschen und danach die Liste an sich löschen.
    Ich möchte erst die Elemente aus der List mit delete[] entfernen, da diese mit new Operator erzeugt wurden und dann am Ende die Liste mit list.clear() entfernen.

    nun wirft er mir aber folgenden Fehler:

    // MEMBER FUNCTIONS FOR _Container_base12
    inline void _Container_base12::_Orphan_all() noexcept {
    #if _ITERATOR_DEBUG_LEVEL == 2
        if (_Myproxy) { // proxy allocated, drain it
            _Lockit _Lock(_LOCK_DEBUG);
    
            for (auto _Pnext = &_Myproxy->_Myfirstiter; *_Pnext; *_Pnext = (*_Pnext)->_Mynextiter) {
                (*_Pnext)->_Myproxy = nullptr;
            }
    
            _Myproxy->_Myfirstiter = nullptr;
        }
    #endif // _ITERATOR_DEBUG_LEVEL == 2
    

    Anhand dieser Meldung gehe ich mal davon aus, dass es ein Problem mit einem nullptr existiert^^

    hier der Code mit dem ich löschen möchte:

    void Remove(SDataBase* _pTownData)
    {
    	
    	string erase;
    
    	system("cls");
    
    	cout << "Geben Sie die Stadt ein, nach der Sie suchen l\224schen m\224chten: ";
    	cin >> erase;
    
    	for (SCity* eraseList : _pTownData->cityList)
    	{
    
    		if (eraseList->name == erase)
    		{
    
    			_pTownData->cityList.remove(eraseList);
    
    		}
    
    
    	}
    
    	_pTownData->counter = _pTownData->counter - 1;
    
    }
    

    und der hier:

    void Finalize(SDataBase* _pTownData)
    {
    
    
    	for (SCity* deleteCity : _pTownData->cityList)
    	{
    
    		delete[] deleteCity;
    
    	}
    
    
    
    	_pTownData->cityList.clear();
    
    }
    

    Und so werden Elemente ich die Liste gepackt:

    void Add(SDataBase* _pTownData)
    {
    
    	SCity* exampleCity = CreateCity();
    
    	_pTownData->cityList.push_back(exampleCity);
    
    	_pTownData->counter = _pTownData->counter + 1;
    
    	cout << "Es wurde eine Stadt zur Database hinzugef\201gt." << endl;
    }
    
    

    Das Programm läuft, außer wenn die Routinen Finalize und Remove verwendet werden, dann kommt immer der oben gezeigt Fehler 🙂

    Ich danke schon mal wieder für eure Hilfe 🙂



  • @Avartos sagte in Structs und listen:

    delete[] deleteCity;

    Und das ist ein Array (d.h. mit new[] allokiert)?



  • Nein ein Array verwende ich ja in dem Sinne nicht.

    SCity* CreateCity()
    {
    
    	SCity* egTown = new SCity();
    
    	cout << endl;
    	cout << "Geben Sie einen Namen f\201r die Stadt ein: ";
    	cin >> egTown->name;
    	cout << endl;
    	cout << "Nun geben Sie das Bundesland ein: ";
    	cin >> egTown->state;
    	cout << endl;
    	cout << "Als letztes geben Sie die Zahl der Einwohner ein: ";
    	cin >> egTown->population;
    	cout << endl;
    
    	return egTown; 
    
    }
    

    so wird das Element für die Liste erzeugt



  • @Avartos Und warum löscht du es als Array?



  • weil delete[] ja dafür da ist Sachen aus dem Speicher zu löschen die mit new erzeugt wurden? Das sagt mir jedes Buch 😃 Erzeugst du was mit new, dann lösche es auch wieder mit delete[]



  • @Avartos sagte in Structs und listen:

    weil delete[] ja dafür da ist Sachen aus dem Speicher zu löschen die mit new erzeugt wurden? Das sagt mir jedes Buch Erzeugst du was mit new, dann lösche es auch wieder mit delete[]

    Nein, das sagt nicht jedes Buch. Jedes Buch, das das sagt, solltest du in den Müll werden.

    Richtig ist:
    Objekte, die mit new erzeugt werden, müssen mit delete gelöscht werden.
    Arrays, die mit new[] erzeugt werden, müssen mit delete[] gelöscht werden.
    Du darfst nicht Objekte, die mit new angelegt wurden, mit delete[] löschen und auch nicht Arrays, die mit new[] erzeugt werden, mit delete löschen.

    Besser: gleich lokale Objekte (ohne Pointer), Objektverwaltungsklassen wie std::vector oder std::list, die du doch am Anfang schon im Code hattest, oder Smartpointer verwenden.



  • öhm...... dann habe ich das falsch umgesetzt. also delete[] für arrays und für new einfache delete?
    dann habe ich mir das falsch gemerkt. Das ist mir gerade sehr sehr peinlich 😃
    Entschuldige bitte @manni66

    okay danke. das hat jetzt funktioniert.

    aber bei der Remove Routine wirft er mir noch einen Fehler. Habt ihr da einen Ansatz, woran es liegen könnte?



  • @wob sagte in Structs und listen:

    Besser: [...] Objektverwaltungsklassen wie std::vector

    Ja, bitte! Die bloße Erwähnung von new[]/delete[] in einem Buch für Anfänger ist schon ein deutliches Zeichen, dass der Autor wohl nur bedingt weiss, was er tut. Nicht nur ist es fehleranfällig, so zu programmieren, die Funktionen sind auch nicht einmal besonders effizient:

    Damit delete[] korrekt arbeiten kann, braucht es nämlich die Größe des Array, da für jedes Element der Destruktor aufgerufen werden muss. Diese Größe muss irgendwo gespeichert werden, da new[] lediglich einen Pointer auf das erste Element zurückgibt. D.h. in vielen Implementationen reserviert new[] einen zusätzlichen size_t vor dem eigentlichen Array und legt dort die Größe ab.

    Das ist insofern ineffizient, da erstens der Programmcode meist ebenfalls wissen muss, wie groß das Array ist und diese Zahl wahrscheinlich auch schon irgendwo speichert. Zweitens ist Speicher, der freigegeben soll, nicht selten bereits kalt, d.h. dessen Inhalt befindet sich nicht mehr im Cache. Dennoch muss delete[] die Größe aus eben diesem Speicherbereich auslesen und erzeugt damit einen Cache Miss, den es wahrscheinlich nicht gäbe, wenn die Größe direkt neben dem Pointer liegt (z.B. auf dem Stack), wie es bei std::vector der Fall ist.

    Nicht einmal die Standardbibliothek verwendet unter der Haube new[]/delete[] und ich habe Zweifel, ob die meisten Implementationen selbst das reguläre new verwenden oder sogar überhaupt delete in irgendeiner Form.

    Meiner Meinung nach können new und delete gerne komplett aus der Sprache verschwinden (und aus Lehrbüchern sowieso). Einzige Ausnahme ist das placement new, das aber bestenfalls in Büchern für Fortgeschrittene und angehende Bibliotheksentwicker Erwähnung finden sollte.

    TL;DR: @Avartos Am besten du vergisst new und delete komplett. Dann musst du dir auch nicht die Unterschiede zwischen den Varianten merken. So ziemlich alle Probleme lassen sich ohne diese garantiert besser lösen.



  • Ich zeige diese Aussage mal meinem Prof und frage mal was er dazu sagt^^
    Wir bekommen das im Studium so beigebracht 😃

    Ich finde new bisher ganz praktisch. ^^
    was gibt es dann für eine effizientere Methode für Structs? @Finnegan



  • @Avartos sagte in Structs und listen:

    was gibt es dann für eine effizientere Methode für Structs? @Finnegan

    Wie hier schon mehrfach erwähnt wurde: Nimm std::vector. Diese Datenstruktur sollte immer die erste Wahl sein, wenn man mehrere Objekte des selben Typs speichern will.

    Einfach und simpel:

    #include <string>
    #include <vector>
    #include <iostream>
    
    struct SCity
    {
        std::string name;
        std::string state;
        int population = 0;
    };
    
    auto main() -> int
    {
        std::vector<SCity> cities;
        
        cities.push_back({ "Erfurt", "Thueringen", 214000 });
        cities.push_back({ "Hannover", "Niedersachsen", 538000 });
        cities.push_back({ "Muenchen", "Bayern", 1450000 });
        
        for (const auto& city : cities)
        {
            std::cout 
                << city.name << ", "
                << city.state << ", "
                << city.population << "\n";
        }
    }
    

    Hartcodierte Einträge direkt im Code lassen sich übrigens auch noch etwas kompakter schreiben:

    std::vector<SCity> cities
    {
        { "Erfurt", "Thueringen", 214000 },
        { "Hannover", "Niedersachsen", 538000 },
        { "Muenchen", "Bayern", 1450000 }
    };
    

Anmelden zum Antworten