Einfach verkettete Liste - Ausgabe nach Kategorie sortiert



  • Hallo zusammen,

    bin neu hier im Forum 🙂 Ich versuche gerade mir C++ etwas beizubringen und bin gerade beim Thema der einfach verketteten Listen. Ich will mir ein Programm schreiben, in welches Rechnungen eingegeben werden können. Die Ausgabe soll nach Monat sortiert sein..

    z.B:
    Monat
    Namen der Rechnungen mit Kategorien
    Alle Beträge des Monats

    Monat2
    Namen der Rechnungen mit Kategorien
    Alle Beträge des Monats

    Hier mein Code:

    class Rechnungen
    {
    
    	friend class Liste;
    
    private:
    
    	float Budget = 500;
    	string Name;
    	string Monat;
    	float Betrag;
    	string Kategorie;
    
    	Rechnungen *Nachfolger;                                           // Verkettungszeiger zum Nachfolger
    
    public:
    
    	Rechnungen() {}                                                  // Standardkonstruktor
    
    	Rechnungen(string nam = { "" }, float betr = 0.0, string mon = { "" }, string kat = { "" }) : Name(nam), Betrag(betr), Monat(mon), Kategorie(kat), Nachfolger(0) {}
    
    	void print() const {
    		cout << "Name: " << Name << endl << "Betrag: " << Betrag << endl << "Monat: " << Monat << endl << "Kategorie: " << Kategorie << endl;
    	}
    
    	Rechnungen *next() {
    		return Nachfolger;
    	}
    
    };
    
    class Liste                                                            // Klasse für verkettete Liste
    
    {
    private:
    
    	Rechnungen *Head;                                                  // Kopfzeiger
    
    public:
    
    	Liste() : Head(0) {}                                               // Konstruktor für leere Liste
    
    	void insert(Rechnungen *kp)                                        // Einfügen einer Rechnung an den Anfang der Liste. Zeiger auf eine Rechnung als Eingabeparameter
    	{ 
    		kp->Nachfolger = Head;                                         // Nachfolger erhält Wert des Kopfzeigers zugewiesen. Nachfolger zeigt jetzt auf 0. Zugriff auf Nachfolger möglich, da friend
    		Head = kp;                                                     // Kopfzeiger erhält Adresse der Rechnung auf das kp zeigt zugewiesen. kp ist jetzt Kopfzeiger .
    	}
    
    	Rechnungen* remove()                                               // Methode remove hat als Rückgabewert einen Zeiger auf ein Konto
    	{
    		Rechnungen *kp = Head;                                         // dem Zeiger kp, der auf eine Rechnung zeigt, wird der Wert des Kopfzeigers zugewiesen, d.h. er zeigt auf den Anfang der Liste
    
    		if (kp != 0)                                                   // Wenn eine Rechnung noch nicht vorhanden ist..
    		{
    			Head = kp->Nachfolger;                                     // Dem Kopfzeiger wird der Wert des Nachfolgers zugewiesen
    			kp->Nachfolger = 0;                                        // der Nachfolger zeigt auf Null
    		}
    
    		return kp;                                                     // Zurückgegeben wird der Zeiger kp
    	}
    
    	Rechnungen* begin() const                                          // Anfang der Liste (Kopf)
    	{
    		return Head;
    	}
    
    };
    
    int main() {
    
    	int auswahl = 1;
    	Liste list;                                                        //Leere Liste erzeugen
    	Rechnungen *kp;
    	int betr = 0;
    	string nam = "";
    	string mon = "";
    	string kat = "";
    	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]: ";
    
    		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;
    
    			// Rechnungen *kp = new Rechnungen(nam, betr); 
    			// list.insert(kp);
    
    			list.insert(new Rechnungen(nam, betr, mon, kat));      // neue Rechnung wird erzeugt - 4 Parameter werden übergeben. Dynamische Erzeugung.
    			break;
    
    		case 2:                                                    // Liste aller Rechnungen
    
    			for (kp = list.begin(); kp != 0; kp = kp->next())
    				kp->print();
    			break;
    
    		case 4:                                                     // Löschen
    
    			list.remove();
    
    			break;
    
    		}
    
    	} while (auswahl != 0);
    
    	return 0;
    
    }
    

    Leider schaffe ich bis jetzt nur alle erstellten "Knoten" der Liste auf einmal, unsortiert ausgeben zu lassen.

    Ich weiß leider nicht wie ich da rangehen soll... kann mir vlt. jemand helfen? 😕



  • Die Liste sortieren?
    Sortiert in die Liste einfügen?



  • manni66 schrieb:

    Die Liste sortieren?
    Sortiert in die Liste einfügen?

    Also es würde 2 Möglichkeiten geben?
    Angenommen, ich würde die noch unsortierte Liste nach der Variablen "Monat" sortieren wollen, wie würde ich da rangehen?

    Ich finde immer nur Beispiele für Sortierung nach Alphabet oder Nummern..



  • Ich habe gleich mehrere Fragen:

    1. Was genau ist der Zweck? Nur das Lernen von verketteten Listen oder soll das auch was echtes werden?

    2. Ich finde es sehr merkwürdig, dass eine Rechnung einen Nachfolger hat. Ist das die Eigenschaft einer Rechnung? Für mich würde das allerhöchstens für sowas wie Folgerechnungen Sinn ergeben, d.h. z.B. für die Telefonrechnung, die jeden Monat kommt. Ansonsten sollte eine Rechnung kein Nachfolger-Pointer haben. Ob die Rechnungen in der Liste, einem Vector oder in was für einem Container auch immer gespeichert sind, sollte nicht das Design der Klasse Rechnung beeinflussen. Das sollte die Klasse "Liste" tun, ggf. im Zusammenhang mit einem einfachen ListNode-Struct, das wie folgt sein könnte:

    struct ListNode {
      Rechnung rechnung;
      ListNode *next;
    };
    

    3. Ich bin generell kein großer Freund von "friend". Insbesondere hier nicht, denn du verkoppelst die Klassen Rechnungen und List (siehe 2), die sich nicht kennen sollten.

    4. Aus deiner entwickelten Liste könntest du als nächsten Übungsschritt ein Template machen, sodass daraus Liste<Rechnung> wird. Und danach auf std::list oder std::vector umstellen 😉

    5. Der Name "begin()" ist verwirrend. Wenn du später einmal mit einer for-Loop über die Liste loopen willst, werden die begin- und end-Funktionen bei der Range-For-Loop aufgerufen und man sollte vom begin zum end kommen können. D.h. ich erwarte von begin einen Iterator auf das erste Element der Liste. Das erste Element dagegen wird in der STL "front" genannt.



  • Erstmal danke für die ausführliche Antwort.

    1. Soll nur zum lernen dienen, nichts echtes 🙂 Ich dachte Rechnungen eignen sich ganz gut, als zweites hätte ich mir noch eine Liste von Autos vorstellen können um diese dann nach Hersteller sortiert auszugeben, etc..

    2. Hab ich verstanden(glaub ich) Der Nachfolger gehört in die List-Klasse, nicht in die Klasse "Rechnungen".

    3. Kannst du mir erklären warum sie sich nicht kennen sollten?

    4. Also du meinst aus den eingegeben Daten ein Template im nachhinein machen?
    Also erst Daten eingeben, dann eine Funktion aufrufen die eine Vorlage draus macht? Bin ja noch sehr frisch in Cpp und muss mich noch genau zu Templates einlesen..

    5. Alles klar, danke...wird geändert.

    Ich versuch mal ein einfaches Beispiel mit Autos zu basteln (ohne Friend-Klasse, mit Template)



  • Chris_85 schrieb:

    Erstmal danke für die ausführliche Antwort.
    1. Soll nur zum lernen dienen, nichts echtes 🙂 Ich dachte Rechnungen eignen sich ganz gut, als zweites hätte ich mir noch eine Liste von Autos vorstellen können um diese dann nach Hersteller sortiert auszugeben, etc..

    Ok, warum nicht Rechnungen! Zum Üben kannst du ja einfach irgendwas nehmen, das dir gefällt 🙂

    3. Kannst du mir erklären warum sie sich nicht kennen sollten?

    Ist eine generelle Sache. Eine Klasse sollte einem (und nur einem) Zweck dienen und nicht irgendwas anderem. Daher verwaltet die Liste irgendwelche Objekte, muss dazu aber nicht die Interna der zu verwaltenden Objekte kennen.

    4. Also du meinst aus den eingegeben Daten ein Template im nachhinein machen? Also erst Daten eingeben, dann eine Funktion aufrufen die eine Vorlage draus macht? Bin ja noch sehr frisch in Cpp und muss mich noch genau zu Templates einlesen..

    Ich verstehe deine Frage nicht richtig.

    Vielleicht schiebst du das mit Templates erstmal nach hinten. Die Idee ist, dass ja jetzt in dem ListNode der Typ "Rechnung" fest verdrahtet ist. Ebenfalls ist in dem "insert" sowie in dem begin (oder front) Rechnung fest im Programm drin. Wenn du nun deine Liste mal für einen anderen Typ, nehmen wir doch dein Beispiel "Auto", verwenden wolltest, müsstest du die Klasse ja umschreiben und alle Rechnung durch Auto ersetzen.

    Durch ein Template ermöglichst du es, dass du deine Liste sowohl für Autos als auch für Rechnungen verwenden kannst. Natürlich nicht beides in derselben List, du hast dann List<Rechnung> für Rechnungen und List<Auto> für Autos.

    Vielleicht machst du erstmal die Liste für Rechnungen fertig. Dann machst du eine für Autos und guckst mal, wo da was verschieden ist. Dann versuchen, das mit einer getemplateten Klasse zu lösen.



  • 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.


Log in to reply