Einfach verkettete Liste - Ausgabe nach Kategorie sortiert



  • So, ich habe mich nochmal reingehängt, Tutorials gelesen und gesehen und alles etwas abgeändert... wie findest Du/Ihr es nun?

    #include <cstdlib>
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    struct Rechnungen
    {
    
    	string Name;
    	string Monat;
    	float Betrag;
    	int Kategorie;
    
    public:
    
    	Rechnungen() {}                                                
    	Rechnungen(string nam = { "" }, float betr = 0.0, string mon = { "" }, int kat = 1) : Name(nam), Betrag(betr), Monat(mon), Kategorie(kat) {}
    
    	void print() 
    	{
    		cout << "Name: " << Name << endl << "Betrag: " << Betrag << endl << "Monat: " << Monat << endl << "Kategorie: " << Kategorie << endl;
    	}
    
    };
    
    struct Knoten
    {
    
    	Knoten *Nachfolger =0;                                           
    	Rechnungen *daten = 0;
    
    public:
    
    	Knoten() {}                                                 
    	Knoten(Rechnungen *daten_) {
    		daten = daten_;
    	}
    
    	void print() const {
    		daten->print();
    	}
    
    	void next(Knoten *Nachfolger_) {
    		Nachfolger = Nachfolger_;
    	}
    
    	Knoten* NachfolgerGeben()
    	{
    		return Nachfolger;
    	}
    
    };
    
    class Liste                                                     
    
    {
    private:
    
    	Knoten *Head;                                              
    	Rechnungen *daten = 0;
    
    public:
    
    	Liste() : Head(0) {}                                              
    
    	void insert(Rechnungen *neue_daten)                                       
    	{
    		Knoten *neuer_anfang = new Knoten(neue_daten);
    		if (Head == 0)
    		{																
    			Head = neuer_anfang;                                                    
    		} 
    
    		else
    		{
    			neuer_anfang->next(Head);
    			Head = neuer_anfang;
    		}
    	}
    
    	void remove()                                             
    	{
    
    		if (Head != 0)                                                  
    		{
    			Knoten *neuer_anfang = 0;
    			if (Head->NachfolgerGeben() != 0) {
    
    				neuer_anfang = Head->NachfolgerGeben();			
    				}
    		 	   delete Head->NachfolgerGeben();
    			   delete Head;
    			   Head = neuer_anfang;
    				}
    
    	}
    
    	void Informationen_ausgeben()
    	{
    
    		if (Head != 0) {
    			Knoten *tmp;
    		    tmp = Head;
    
    				while (tmp->NachfolgerGeben() != 0)
    				{
    					tmp->print();
    					tmp = tmp->NachfolgerGeben();
    				}
    
    		tmp->print();
    	}
       }
    
    };
    
    int main() {
    
    	int auswahl = 1;
    	Liste list;                                                        //Leere Liste erzeugen 
    
    	float betr = 0;
    	string nam = "";
    	string mon = "";
    	int kat = 1;
    
    	do
    	{
    
    		cout << endl << "---- Rechnungen verwalten ----" << endl;
    		cout << "1. Neue Rechnung anlegen" << endl;
    		cout << "2. Rechnungen ausgeben" << endl;
    		cout << "3. Summe Kategorien" << endl;
    		cout << "4. letzte Rechnung loeschen" << endl;
    		cout << "0. Programm beenden" << endl;
    		cout << "> ihre Wahl [Return]: " <<endl;
    		cin >> auswahl;
    		cout << endl;
    
    		switch (auswahl)
    
    		{
    
    		case 0:
    			cout << "Bye Bye" << endl;
    			break;
    
    		case 1:                                                      // Anlegen 
    
    			cout << "> Name: " << endl;
    			cin >> nam;
    			cout << "> Betrag: " << endl;
    			cin >> betr;
    			cout << "> Monat: " << endl;
    			cin >> mon;
    			cout << "> Kategorie: " << endl << "1: Miete" << endl << "2: Essen" << endl << "3: Urlaub" << endl << "4. Party" << endl;
    			cin >> kat;
    
    			list.insert(new Rechnungen(nam, betr, mon, kat));      // neue Rechnung wird erzeugt - 4 Parameter werden übergeben. Dynamische Erzeugung. 
    			break;
    
    		case 2:                                                    // Liste aller Rechnungen 
    
    			list.Informationen_ausgeben();
    			break;
    
    		case 4:                                                     // Löschen 
    
    			list.remove();
    			break;
    
    		}
    
    	} while (auswahl != 0);
    
    	return 0;
    	}
    

    Mein Problem ist noch folgendes:

    Angenommen, ich lege 5 Rechnungen an, davon 3 mit der Kategorie "1". Nun möchte ich nur die Rechnungen ausgeben lassen, welche Kategorie 1 sind.

    Folgendes habe ich versucht in meiner Methode "Informationen_ausgeben()" zu ergänzen:

    Rechnungen *kap;
    			if (kap->Kategorie == 1)
    			{
    				jetzt die while schleife...
    			}
    

    Funktioniert leider nicht, da kap nicht inizialisiert ist. Wie muss ich kap initialisieren? Ich hab da irgendwas mit Zeigern grundlegend noch nicht verstanden 😞



  • also einem zeiger musst du natürlich einen wert zuweisen. entweder mittels new (z.b. kap = new Rechnungen()) oder indem du die adresse eines bestehenden objektes zuweist, z.b. kap = &rechnung1.

    und dann brauchst du keine lokalen objekte, zeiger usw. in der ausgabe-funktion, du durchläufst einfach die liste und wenn die kategorie übereinstimmt, gibst du das datum aus und ansonsten gehst du einfach weiter zum nächsten element.



  • HansKlaus schrieb:

    also einem zeiger musst du natürlich einen wert zuweisen. entweder mittels new (z.b. kap = new Rechnungen()) oder indem du die adresse eines bestehenden objektes zuweist, z.b. kap = &rechnung1.

    Das verstehe ich leider noch nicht...

    Wenn ich folgendes versuche:

    void Informationen_ausgeben()
    	{
    
    		if (Head != 0) {
    			Knoten *tmp;
    		    tmp = Head;
    
    			Rechnungen *kap;
    			kap = new Rechnungen();
    
    				while (tmp->NachfolgerGeben() != 0)
    				{
    					if (kap->Kategorie == 1)
    					{
    					tmp->print();
    					tmp = tmp->NachfolgerGeben();
    					}
    				}
    
    		tmp->print();
    	}
       }
    

    bekomme ich die Fehlermeldung:
    Error C2668 'Rechnungen::Rechnungen': ambiguous call to overloaded function

    Zum 2. Vorschlag: kap = &rechnung1
    Ich meinem Verständnis erzeuge ich die Objekte ja erst, sobald ich über die insert Funktion eine neue Rechnung eintippe, woher weiß ich das diese dann z.B. rechnung1 heisst? 😕



  • HansKlaus schrieb:
    also einem zeiger musst du natürlich einen wert zuweisen. entweder mittels new (z.b. kap = new Rechnungen()) oder indem du die adresse eines bestehenden objektes zuweist, z.b. kap = &rechnung1.

    Das verstehe ich leider noch nicht...

    mach dir nichts draus, HansKlaus versteht es auch nicht...



  • vielleicht solltest du dich erst einmal mit der sprache selbst (z.b. mit zeigern) beschäftigen und dich dann an höhere dinge wie listen wagen.

    jedenfalls hast du in Liste einen (hoffentlich gültigen) zeiger auf Knoten, in Knoten einen (hoffentlich auch gültigen) zeiger auf Rechnungen und in Rechnungen das Attribut Kategorie (welches du meiner meinung nach übrigens klein schreiben solltest, um verwechslungen mit klassen zu vermeiden).

    jetzt greifst du in deiner schleife immer auf dieses attribut Kategorie zu, wertest es aus, und abhängig von der auswertung gibst du dann alles auf dem bildschirm aus und erhöhst den den zeiger tmp um 1.

    edit: dieses kap brauchst du eigentlich gar nicht, da du über tmp->daten->kategorie direkt auf den wert zugreifen kannst.



  • Zunächst drei Sachen, die mit dem eigentlichen Problem nichts zu tun haben:

    1. Gewöhn' dir an, Klassen und Funktionen in separate Quelltext-Dateien zu untergliedern. In deinem Fall bietet sich eine Aufteilung in eine Rechnung.cpp und eine List.cpp an. Zu jeder .cpp gehört in der Regel eine .h Datei, in der Funktionen und Klassen deklariert werden, aber keinen Code enthalten. Der wird dann in der .cpp implementiert. Für Rechnung kann das dann so aussehen:

    Rechnung.h

    #ifndef RechnungH // Das hier sind Header Guards, die mehrfaches Inkludieren 
    #define RechnungH // für eine Übersetzungseinheit verhindern.
    
    #include <string>
    
    class Rechnung
    {
       // ob die Wahl der Datentypen sinnvoll ist oder nicht beachte ich
       // hier nicht, es geht ja primär um die Liste. Prinzipiell finde ich
       // std::string für den Monat und float für den Betrag unpassend, weil
       // ein Monat sich besser durch eine Zahl von 1-12 darstellen lässt. Oder
       // noch besser durch einen geeigneten Datumstypen, der ein komplettes 
       // Datum enthalten kann.
       // float für Geldbeträge ist unpassend, da sich durch inhärente Ungenauigkeiten
       // des float Datentyps Differenzen bei Rechenoperationen ergeben können.
       // Möglicherweise ergibt 2 * 1.2 nicht 2.4 sondern 2.39999 oder 2.40001.
       std::string Name_;
       std::string Monat_;
       float Betrag_;
       int Kategorie;
    
    public:
       Rechnung();
       Rechnung( const std::string& Name, const std::string& Monat, float Betrag, int Kategorie );
    
       // getter/setter Methoden für Elemente. Wenn für jedes Element ein
       // getter/setter existiert, der nur den Wert zurückgibt bzw. setzt kann
       // man sich überlegen, ob diese Elemente auch public gemacht werden können.
       // Einzeilige getter/setter Methode kann man auch in der .h Datei implementieren,
       // um dem Compiler zu helfen, aber ich will in meinem Beispiel nicht gegen die
       // oben genannte Regel verstosssen (Trennung .h/.cpp Datei) ;)
       const std::string& name() const;
       void set_name( const std::string& Name );
    
       const std::string& monat() const;
       void set_monat( const std::string& Monat );
    
       float betrag() const;
       void set_betrag( float Betrag );
    
      int kategorie() const;
      void set_kategorie( int Kategorie );
    };
    #endif
    

    Rechnung.cpp

    #include "Rechnung.h"
    
    using namespace std;
    
    Rechnung::Rechnung() :
       Betrag_( 0.0f ),
       Kategorie( 0 )
    {
    }
    
    Rechnung::Rechnung( const string& Name, const string& Monat, float Betrag, int Kategorie ) :
       Name_( Name ),
       Monat_( Monat ),
       Betrag_( Betrag ),
       Kategorie_( Kategorie )
    {
    }
    
    const string& Rechnung::name() const
    {
       return Name_;
    }
    
    void Rechnung::set_name( const string& Name )
    {
       Name_ = Name;
    }
    
    // snip. Der Rest ist Fleißarbeit.
    
    1. Die Schlüsselwörter struct und class sind in C++ nahezu identisch. Der einzige Unterschied ist, dass bei struct per Default alle Elemente public sind, während bei class alle Elemente per Default private sind. Einen public Abschnitt in einem struct zu deklarieren ist also überflüssig.

    2. Formatier´ deinen Quelltext vernünftig und entferne überflüssige Leerzeilen.

    3. Versteif´ dich nicht auf Zeiger. In C++ geht recht viel ohne Zeiger, wenn man Referenzen benutzt. Wenn du etwas weiter bist solltest du dich über Iteratoren schlau machen.

    Und jetzt zu deinem Code:

    1. Benutze das Schlüsselwort nullptr für ungültige Zeiger.
    2. Informiere dich über const-correctness
    3. Die Liste sollte nur nur Zeiger auf Knoten enthalten, aber keinen Zeiger auf ein tatsächlich verwaltetes Element (siehe dein Quelltext, Zeile 62 ist überflüssig).
    4. Du musst dir Gedanken über die Lebenszeit deiner Objekte machen. Wenn du nur Zeiger in der Liste speicherst kann es passieren, dass die Objekte, auf die deine Zeiger zeigen, out of scope laufen und zerstört werden. Dann zeigen deine Zeiger auf ungültigen Speicher und dein Programm erzeugt undefiniertes Verhalten.
      Beispiel mit deiner Listen-Klasse:
    void erzeuge_rechnung( Liste& l )
    {
       // r lebt nur in dieser Funktion und ist nach dem Verlassen der Funktion
       // ungültig. Damit ist die Adresse, die in der Liste gespeichert ist, auch
       // ungültig und erzeugt beim Zugriff auf sie undefiniertes Verhalten.
       Rechnung r( "Peng!", "Januar", 123.45f, 0 );
       l.insert( &r );
    }
    

    Eine Lösung dazu könnte sein, dass die Liste Kopien von Rechnungen verwaltet (naiver Ansatz):
    (entgegen der o.g. Regel alles in einem Rustch, zu faul zum Tippen).
    Die hier unten gezeigte Listenklasse benutzt manuelle Speicherverwaltung, was man eigentlich vermeiden
    sollte. Aber ich denke, zu Lehrzwecken und um Basiswissen zu vermitteln kann man das hier ruhig machen.
    Später solltest du auf smart pointer der STL zurückgreifen, die dir bei der Speicherverwaltung
    vieles abnimmt und dein Programm robuster macht.
    Als Faustregel bei der manuellen Speicherverwaltung gilt: zu jedem new gehört genau ein delete Aufruf.

    struct Node
    {
       Node*     Next;
       Rechnung  Rech;
    
       Knoten( Node* N, const Rechnung& R ) :
          Next( N ),
          Rech( R )
       {
       }
    };
    
    class List
    {
       Node* Head_;
    
    public:
       Liste() :
          Head_( nullptr )
       {
       }
    
       // hier machen sich die Folgen der Benutzung manueller Speicherverwaltung bemerkbar:
       // Jeder Zeiger hat genau einen Besitzer, der für das Freigeben des Speichers verantwortlich
       // ist. Der Kopierkonstruktor kopiert ein Objekt elementweise, sodass die Kopie und das
       // Original die gleichen Zeiger besitzen. Und weil sowohl Original als auch Kopie für 
       // das Freigeben der Zeiger verantwortlich sind wird jeder Zeiger zwei Mal freigebeben
       // (ein mal vom Original, ein Mal von der Kopie), und das erzeugt undefiniertes Verhalten.
       ~List() 
       {
          // jeder Zeiger muss manuell freigeben werden
          while( Head_ )
          {
             Node* tmp= Head_;
             Head_ = Head_->Next;
             delete tmp;  
          }
       }
    
       // Kopien der Klasse verbieten (aus o.g. Gründen)
       List( const List& othert ) = delete;
       List& operator=( const List& othert ) = delete;
    
       void insert( const Rechung& r )
       { 
          // fügt einen neuen Knoten vorn in die Liste ein. Der Knoten speichert eine Kopie der Rechung.
          Head_ = new Node( Head_, r );
       }
    
       size_t size() const
       {
          size_t s = 0;
          Node* tmp = Head_;
          while( tmp )
          {
             ++s;
             tmp = tmp->Next;
          }
          return s;
       }
    
       Rechnung& get( size_t Index )
       {
          // n. Rechnung der Liste zurückgeben. Keine Überprüfung auf Gültigkeit des Index.
          Node* tmp = Head_;
          for( size_t i = 0; i < Index; ++i )
          {
             tmp = tmp->Next;
          }
          return tmp->Rech;
       }
    };
    
    1. Ersetze die print() Methode von Rechnung durch eine Überladung des ostream::operators<< . Damit kannst du Rechnung
      C++ konform in jeden ostream (zB cout , aber auch ofstream ) ausgeben.
    #include <iostream>
    
    class Rechnung
    {
       ...
       friend std::ostream& operator( std::ostream& os, const Rrechnung& r );
    };
    
    std::ostream& operator( std::ostream& os, const Rrechnung& r )
    {
       oss << r.name() << ", " << r.monat() << ", " << r.betrag() << ", " << r.kategorie();
    }
    
    int main()
    {
       Rechnung r;
       std::cout << r << \r\n";
    }
    

    Ich denke, das reicht erst mal 😉
    Frag ruhig, wenn etwas unklar ist.



  • @HansKlaus:

    Vielen Dank, mit if (tmp->daten->Kategorie==1) tut das Programm genau was ich will.

    @DocShoe:

    Wow, danke für die ausführliche Hilfe. Ich werde Deine Vorschläge/Tipps step-by-step durcharbeiten 👍



  • Hallo Chris_85,

    ich hätte eine ähnliche Frage bzw. Bitte zu diesem Thema.
    Könntest du dich bitte bei mir per Email melden?
    riceornoodles@web.de
    ich kann dich leider hier nicht anschreiben... Vielen lieben Dank

    Liebe Grüße
    Tanja



  • @Tanja
    Warum fragst du nicht einfach hier?



  • @tanjat

    @docshoe sagte in Einfach verkettete Liste - Ausgabe nach Kategorie sortiert:

    @Tanja
    Warum fragst du nicht einfach hier?

    Aber dann bitte einen neuen Thread aufmachen.
    Ein Verweis auf diesen hier kannst du ja machen.



  • Ich habe laut c-plusplus.net, leider keine Berechtigung Ihm persönlich zu schreiben... wieso auch immer? Kennt ihr vielleicht den Grund? "Du verfügst nicht über ausreichende Berechtigungen, um die Aktion durchzuführen."


  • Mod

    @tanjat Was soll der Unsinn? Du sollst keine Forenmitglieder mit PMs belästigen, weil Du eine Frage hast—die gehören ins Forum. Und bitte keine Nekromantie.


Anmelden zum Antworten