Datenaustausch zwischen Funktionen (Greedy-Algorithmus)



  • Hallo! 🙂

    Seit 2 Monaten setze ich mich nun mit der prozeduralen C++-Programmierung auseinander. Im Rahmen von Übungsaufgaben habe ich inzwischen ein Programm zur Umsetzung des Greedy-Algorithmus aus dem Operations Research entwerfen können, welches in der Lage ist Daten aus einer Textdatei einzulesen, zu berechnen und wieder als Textdatei auszugeben. Bisher habe ich das alles mit globalen Variablen gelöst.

    Nun lautet die Vorgabe aber sich von den globalen Variablen zu entfernen und alles mit lokalen Variablen zu realisieren, wobei der Datenaustausch mit formalen und aktuellen Parametern und ggf. mit Zeigern zu erfolgen hat. Das stellt für mich eine riesige Herausforderung da. Es fehlt mir zudem überhaupt an einem Ansatz und nachdem ich nun Stunden vor dem Code hing wollte ich euch mal nach Hilfe fragen! Ihr würdet mir wirklich sehr helfen, wenn ihr mich da weiter bringen würdet und mir zeigen könntet wie man das beispielsweise umsetzen könnte.

    Mein Code lautet wie folgt:

    // Include-Anweisungen
    #include <iostream>        // cin, cout - Für Eingabe- und Ausgabestream
    #include <iomanip>        // setw, setprecision - Für die Begrenzung der Nachkommastellen
    #include <fstream>        // Notwendig um die Streamklasse für die Datenbearbeitung nutzen zu können
    #include <conio.h>
    
    using namespace std;    // Zum Abkürzen der langen Befehle std::cout, std::cin
    
    // Prototypen
    void dateieingabe(void);
    void berechnung(void);
    void dateiausgabe(void);
    
    // Globale Variablen und Streams
    // Konstanten
    
    const int MAXITEMS = 100;    // Maximale Anzahl Items
    
    // globale Variablen
    
    int    anzahl,                    // Anzahl an Items
        items_im_rucksack,        // Eingepackte Gegenstände
        i,                        // Itemindex
        j,                        // Vergleichsindex
        c;                        // Kapazitätsrestriktion
    
    double summe_p,                    // Summe der p-Werte (sollen maximiert werden)
           temp,                    // Temporäre Variable für Selectionsort
           rucksack,                // Tatsaechlicher genutzter Platz
           restplatz_im_rucksack;    // Entspricht zu Beginn c, wird reduziert durch in Rucksack gepackter Items
    
    // Vektoren
    
    int       Items[MAXITEMS];            // Nummern der Items
    double w[MAXITEMS],                // Gewichte
           p[MAXITEMS],                // Itemprofit, dessen Summe maximimal werden soll
           quot[MAXITEMS];            // Greedy-Bewertung der einzelnen Gegenstände (Quotient aus p/w)
    
    // Streams
    
    ifstream    datei1;                // Beschreibt den Einlese-Stream
    ofstream    datei2;                // Beschreibt den Textdatei-erstellenden Stream / Ausschreibenden-Stream
    
    // Char-Variablen
    
    char        zeile[81];            // max. 80 Zeichen pro Zeile für Kommentare
    
    void main(void)
    {
    
        // Bildschirm-Information
        cout << "\nProgramm zur Anwendung des Greedy-Algorithmus zur Loesung eines 0/1-Rucksack Problems.\n\n";
    
        // Funktionsaufruf für Dateieingabe
        dateieingabe();
    
        // Funktionsaufruf für Berechnung
        berechnung();
    
        // Funktionsaufruf für Dateiausgabe
        dateiausgabe();
    
        system("pause");
    }
    
    // Funktion liest Textdatei ein und schreibt Werte für Vektoren und Kapazität
    void dateieingabe(void)
    {
    
        // Benutzerdefinierte Textdatei öffnen
        cout << "Hinweis: Bitte sicherstellen, dass sich die Input-Datei im Verzeichnis befindet\nund der Dateiname 'input_items.txt' lautet. ";
        cout << "Zum Einlesen der Datei bitte Taste druecken...\n";
            _getch();
    
        datei1.open("input_items.txt");
    
        // Zustand der Textdatei prüfen
        if (! datei1)
        {
            cout << "\nFehler beim Einlesen der Datei input_items.txt!\n\n";
            system("pause");
        }
    
        // Benutzerdefinierte Textdatei lesen
        datei1.getline(zeile,81);                       
        cout << "\n" << zeile << endl;
        datei1 >> anzahl;                                    // Weist der n-Variable ihren Wert zu - Bsp. 1) wird n = 8; Bsp. 2) wird n = 30
        cout << "Anzahl an Items: " << anzahl << endl;
        datei1.ignore();
        datei1.getline(zeile,81);
        cout << "\n" << zeile << endl;
        datei1 >> c;                                    //Weist der c-Variable ihren Wert aus Datei zu
        cout << "Kapazitaet des Rucksacks: " << c << endl;
        datei1.ignore();
    
        datei1.getline(zeile,81);
        cout << "\n" << zeile << endl;
        for (i = 1; i <= anzahl; i++)
        {
            datei1 >> Items[i-1] >> p[i-1] >> w[i-1];    // Schreibt die Werte aus der Textdatei in die "Vektoren"
    
            // Kontrollausgabe
            cout << "\n" << Items[i-1] << " ";
            cout << fixed << setprecision(1) << p[i-1] << " " << w[i-1];
    
            if (i >= anzahl)
            {
                cout << "\n\n" << "Alle Vektoren gefuellt. Anzahl der definierten Vektoren betraegt " << anzahl;
                cout << "\n\n\n" << "Naechster Schritt: Anwendung des Greedy-Algorithmus - Quotientenbildung";
                cout << "\n\n";
                _getch();
    
            }
        }
    }
    
    // Funktion berechnet Quotienten, Sortiert die Ergebnisse absteigend und befüllt Rucksack bis dieser voll ist
    void berechnung(void)
    {
    
        // Quotientenbildung
        for (i = 1; i <= anzahl; i++)
        {
            quot[i-1] = p[i-1]/w[i-1];
            cout << "\n";
            cout << fixed << setprecision(1) << quot[i-1];
    
            if (i >= anzahl)
            {
                cout << "\n\n" << "Alle Quotienten gebildet!\n\n\n";
                cout << "Naechster Schritt: Anwendung des Greedy-Algorithmus - absteigende Sortierung mittels 'Selection Sort'\n\n";
    
                _getch();
            }
        }
    
        // Absteigende Sortierung nach "Selectionsort"- Ausgabe in Gewicht und Sortierung nach Quotient
        for(i=0; i<anzahl-1; i++)           
        {
            for(j=i+1;j<anzahl; j++)
            {
                if (quot[i] < quot[j])
                {
    
                    // Tausch der Variablen
                    temp = w[j];           
                    w[j] = w[i];
                    w[i] = temp;
                    temp = p[j];
                    p[j] = p[i];
                    p[i] = temp;
    
                    temp = quot[j];
                    quot[j]=quot[i];
                    quot[i]=temp;
    
                }
            }
        }       
    
        cout << "Ende - ";
        cout << "Sortierung beendet!\n\n";
    
        for (i=0; i<anzahl; i++)
        {
            if (i<anzahl)
            {
                cout << w[i] << " ";
            }
        }
    
        cout << "\n\n\n";
        cout << "Naechster Schritt: Befuellen des Rucksacks\n\n";
    
        _getch();
    
        // Gegenstände in Rucksack einpacken
        restplatz_im_rucksack = c;
        rucksack = 0;
        summe_p = 0;
        items_im_rucksack = 0;
    
        for (i = 0; i < anzahl; i++)
        {
    
            if (restplatz_im_rucksack - w[i] >= 0)
            {
                restplatz_im_rucksack = restplatz_im_rucksack - w[i];
                rucksack = rucksack + w[i];
                summe_p = summe_p + p[i];
                items_im_rucksack = items_im_rucksack + 1;
            }
    
            if (restplatz_im_rucksack - w[i] < 0)
            {
                break;
            }
    
        }
    
        if ((i = anzahl) || (rucksack = c))
        {                                   
                cout << "Anzahl in Rucksack verstauter Items: " << items_im_rucksack << "\n";
                cout << "Belegter Platz im Rucksack: " << rucksack << "\n";
                cout << "Ungenutzter Platz im Rucksack: " << restplatz_im_rucksack << "\n";
                cout << "Gesamtprofit: " << summe_p << "\n\n" << "Fuer Dateiausgabe Taste druecken...\n\n";
    
                _getch();
    
                cout << "Ausgabe in Textdatei wird initialisiert...";
        }
    }
    
    // Funktion gibt die berechneten Ergebnisse als Textdatei aus
    void dateiausgabe(void)
    {
    
        // Ergebnisse als Textdatei ausgeben
        // Öffnen des Ausgabestreams
            datei2.open("items_output.txt", ios::trunc);                // ios::trunc weist an eine vorhandene Datei zu überschreiben
    
            // Ausgabe von Titelzeile und Ergebnissen / Schreiben der Ergebnisse
            datei2 << "Lösung des 0/1-Rucksack-Problems\n";
            datei2 << "\nAnzahl beruecksichtigter Items: ";
            datei2 << items_im_rucksack << endl;
            datei2 << "\nGesamtprofit: " << fixed << setprecision(1) << summe_p << endl;
            datei2 << "\nGesamtgewicht: " << fixed << setprecision(1) << rucksack << endl;
    
            datei2.close();
    
            // Ausgabe des Endes des Programmes
            cout << "\nErgebnisliste als 'items_output.txt' ausgegeben\nBerechnung beendet.\n";
    
    }
    

    Hat jemand eine Idee wie ich den Datenaustausch zwischen den Funktionen ermöglichen kann, wenn die globalen Variablen nicht herangezogen werden (und das möglichst über formale/aktuelle Parameter)?

    Viele Grüße!

    Xalion



  • Du musst dir anschauen, welche Informationen du in der Funktion (von außen) brauchst.
    Diese übergibst du als Parameter an die Funktion.

    Zudem musst du wissen, welche Information die Funktion liefert und welche Daten in der Funktion verändert werden?
    Daran orientiert sich, wie die Daten übergeben, bzw zurück gegeben werden.

    Möglicherweise ist es besser, die Funktionen noch weiter zu unterteilen, damit nur eine Sache geändert wird.
    (Du hast die ja schon teilweise kommentiert)

    Interaktion mit dem User ist meist nicht Aufgabe dieser einfachen Funktionen.
    Diese geben einen Fehlerstatus zurück, auf den die rufende Funktion reagiert.



  • Zudem ist dein jetziger Auflauf nicht in Ordnung!

    Wenn in dateieingabe beim öffnen der Datei ein Fehler auftritt, gibst du zwar eine Meldung aus, aber wenn der Nutzer eine Taste drückt läuft das Programm weiter.

    Obwohl du keine Daten lesen kannst, die Berechnung ohne Daten sinnlos ist, ebenso die Ausgabe.



  • Du kannst ja mal mit einfachen Dingen anfangen: suche diejenigen Variablen, die ausschließlich innerhalb einer Funktion verwendet werden. Diese brauchen dann nicht global zu sein (zumindest wenn die Werte nicht zwischen zwei Berechnungen gespeichert werden sollen). Bei dir trifft das z.B. auf die Variable "quot" zu, die du ausschließlich in berechnung verwendest. Diese kannst du schon mal dahin verschieben.

    Auch die Variablen "datei1" und "datei2" sollten nur in der jeweiligen Funktion deklariert werden, wo sie auch benutzt werden. Das spart dir auch gleich die Kommentare zu den Variablen.

    Dann noch ein paar weitere Bemerkungen:

    * Du kommentiertst falsch:

    // Vektoren
     
    int       Items[MAXITEMS];            // Nummern der Items
    (...)
    

    Das sind keine Vektoren, sondern C-style Arrays (warum verwendest du keine std::vector<>en?)

    * Wenn du 2 Variablen tauschen möchtest (wie in berechnung), dann nimm std::swap. Die Variable "temp" kann somit komplett verschwinden.

    * Bei selbstgemachter Sortierung frage ich mich immer, warum nicht std::sort?

    * Bei Funktionen, die keine Parameter nehmen, kannst du einfach () schreiben, das "void" innerhalb der Klammern ist überflüssig.

    * Die Überprüfung if (i >= anzahl) innerhalb der Quotienbildung-Schleife über i ist überflüssig: der Code sollte einfach ohne das if hinter der Schleife stehen.

    * Keine Zeit für mehr Anmerkungen... 😉



  • Globale Variablen sind schlecht, verwende lieber lokale Variablen, Argumente und Rückgabewerte. System("pause") ist auch dringend zu vermeiden, nimmt cin.get(), um auf Benutzereingaben zu warten, oder lass es gleich weg, schließlich ist es eine Konsolenanwendung.



  • Wow, so viele Anregungen hätte ich nicht erwartet, vielen Dank für die vielen nützlichen Tipps! 🙂

    Ich lerne C++ im Rahmen eines Programmierkurs für Wirtschaftswissenschaftler (inhaltlich geht es also nicht allzu tief in die Thematik hinein) und übe mithilfe von Übungsaufgaben, die mir relativ wenig Freiraum lassen. Die Funktionen noch weiter zu unterteilen würde nicht mehr der Vorgabe entsprechen, wenngleich ich die Sinnhaftigkeit für die Übersichtlichkeit einsehe. Jedenfalls danke DirkB für den Ansatz! Ich werde nun zunächst mal versuchen die nötigen Variablen (mit Ausnahme der Arrays - das lasse ich noch kurz außen vor) lokal in der Main zu deklarieren, diese per aktuelle Parameter an die formalen Parameter der aufzurufende Funktion weiterzugeben und danach die Werte wieder an die aktuellen Parameter zurück zu geben (mit return). Das ist jetzt erstmal mein Ansatz.

    Die Frage warum ich kein "std::vector<>en" verwende liegt einfach darin, weil mir das nicht beigebracht wurde 😃

    Wenn ich std::swap verwende, funktioniert das mit diesen Arrays genau so wie mit "einfachen" Variablen? Wäre demnach "std::swap (w[i], w[j])" zulässig?
    Und das setzt, wenn ich richtig recherchiere "#include <algorithm>" voraus, richtig? Auf diesem Wege würde ich mir die Temp-Variable ja definitiv sparen können.



  • Xalion schrieb:

    Die Funktionen noch weiter zu unterteilen würde nicht mehr der Vorgabe entsprechen, wenngleich ich die Sinnhaftigkeit für die Übersichtlichkeit einsehe.

    Ist die Aufgabe so gestellt, dass du keine anderen Funktionen definieren darfst?
    Du benutzt in deiner Funktion ja auch schon andere (Standard-)Funktionen.
    Deine Sortierung schreit geradezu nach einer eigenen Funktion.

    Xalion schrieb:

    ...(mit Ausnahme der Arrays - das lasse ich noch kurz außen vor) ...

    Gerade die Arrays sollten da am wenigsten Probleme machen.



  • Xalion schrieb:

    Wenn ich std::swap verwende, funktioniert das mit diesen Arrays genau so wie mit "einfachen" Variablen? Wäre demnach "std::swap (w[i], w[j])" zulässig?

    Versuch macht klug! (ja!)

    Wobei: beim Programmieren sollte ich vielleicht besser nicht zum Versuch raten, sondern zur Doku: http://www.cplusplus.com/reference/utility/swap/?kw=swap - also warum sollte es nicht für 2 Variablen aus einem Array gehen?

    Xalion schrieb:

    Und das setzt, wenn ich richtig recherchiere "#include <algorithm>" voraus, richtig? Auf diesem Wege würde ich mir die Temp-Variable ja definitiv sparen können.

    Seit C++11: <utility>.



  • Danke DirkB und wob für eure Antworten. Ja, der Versuch macht bekanntlich klug 😃 Ich werde mir die Dokumentation dazu angucken, sobald ich mein Problem mit dem Datenaustausch lösen konnte. 🙂

    @DirkB: Leider ist die Aufgabe genau so gestellt. Es werden am Ende Punkte vergeben für die Einhaltung der Vorgaben und die Vorgabe lautet:

    Erstellen Sie für die von Ihnen implementierte Heuristik eine Programmversion mit einer main-Funktion und je einer Funktion für die Dateneingabe, die Berechnung der Lösung und die Datenausgabe.

    Ich habe mich jetzt mal daran versucht einen Datenaustausch zwischen der Main-Funktion und der "dateneingabe"-Funktion zu realisieren und habe den restlichen Code erstmal auskommentiert.

    // Include-Anweisungen
    #include <iostream>		// cin, cout - Für Eingabe- und Ausgabestream
    #include <iomanip>		// setw, setprecision - Für die Begrenzung der Nachkommastellen
    #include <fstream>		// Notwendig um die Streamklasse für die Datenbearbeitung nutzen zu können
    #include <conio.h>
    
    using namespace std;	// Zum Abkürzen der langen Befehle std::cout, std::cin
    
    // Prototypen
    int dateieingabe(int anzahl, int c, int i);
    
    // Funktionen
    void main(void)
    {
    
    int anzahl = 0, c = 0, i = 0;
    
    dateieingabe(anzahl, c, i);
    
    cout << "\n" << anzahl << " " << c << " " << i;
    
    	_getch();
    }
    
    int dateieingabe(int anzahl, int c, int i)			
    {
    
    anzahl = 8;
    		c = 100;
    		i = anzahl;
    
    		cout << anzahl << " " << c << " " << i;
    
    		return anzahl, c, i;
    }
    

    Leider gibt das Programm zwar innerhalb der "dateieingabe"-Funktion die richtigen Variablenwerte aus, gibt sie aber nicht an die gleichnamigen Variablen in der Main-Funktion zurück. Meine Frage lautet nun: Was übersehe ich denn da?
    Ich habe das Gefühl die Funktionsweise der Rückgabe von Werten an Variablen der Main-Funktion noch nicht so ganz verstanden zu haben und werde definitiv noch viel dazu lesen.
    Eventuell erschließt sich mir dann auch, warum das gerade mit Arrays noch einfacher sein soll 🙂

    Ich würde mich sehr über weitere Hilfestellung freuen! Ich denke, wenn ich das Prinzip verstanden habe lässt sich die Lösung leicht von mir selbst auf alle restlichen Programmteile übertragen 🙂



  • Deine Lernphase scheint mir sehr sinnfrei zu sein, erst programmierst du einfach drauf los um dann doch wieder komplett anders weiter zu machen.
    Unsinn zu lernen/lehren und von vornherein wissen, das es anschließend wieder verworfen wird, ist schizophren.
    Kann sein, dass du nichts dafür kannst, ändert aber nichts an der Sachlage.

    - vorab solltest du wissen, ob du C oder C++ programmieren willst
    (was in der Folge dann konsequent auch konsequent benutzt wird), in C nimmst du ein Array/dyn.Speicherbereich, in C++ einen vector usw.)
    - zuerst kommt immer das Datendesign, deinen Müll

    int       Items[MAXITEMS];            // Nummern der Items
    double w[MAXITEMS],                // Gewichte
           p[MAXITEMS],                // Itemprofit, dessen Summe maximimal werden soll
           quot[MAXITEMS];            // Greedy-Bewertung der einzelnen Gegenstände (Quotient aus p/w)
    

    fasst man in eine struct (in C++ auch class) zusammen, z.B.

    struct Rucksack {
    int i;
    double w,p,quot;
    };
    

    - für diese struct definierst du dann ein Array/dyn-Speicherbereich(Zeiger) oder einen Vector

    struct Rucksack liste[MAXITEMS];
    bzw.
    struct Rucksack *liste;
    bzw.
    vector<Rucksack> liste;
    

    diese Liste definierst du dann in main und übergibst sie an deine Funktionen.
    Das ist absolut simpel.

    int main()
    {
    struct Rucksack liste[MAXITEMS];
    int z = 0;
    f(liste,&z);
    }
    
    int main()
    {
    vector<Rucksack> liste;
    f(liste);
    }
    

    Wenn du den Inhalt der Liste in der Funktion verändern willst, musst du diese Größe (nicht die Absolutgröße) mitgeben (C) bzw. in C++ eine Referenz auf die Liste(vector) benutzen.



  • Du kannst mit return nur einen Wert zurückgeben, das Komma macht etwas anderes als du hier vermutest.

    Willst du mehrere Daten zurückgeben, dann musst du das durch eine Klasse, Struktur, o.ä. realisieren. Alternativ kannst du auch mit Zeigern oder Referenzen arbeiten.

    void dateieingabe(int& anzahl, int& c, int& i)         
    {
     anzahl = 8;
     c = 100;
     i = anzahl;
    }
    

    Wenn du die Funktion nun so aufrufst,

    int anzahl = 0, c = 0, i = 0;
    
    dateieingabe(anzahl, c, i);
    

    dann sollten anzahl, c und i die richtigen Werte haben.

    Oder mit einer Struktur:

    struct Eingabe
    {
     int anzahl;
     int c;
     int i;
    };
    
    Eingabe dateieingabe(int anzahl, int c, int i)         
    {
     Eingabe e;
    
     e.anzahl = 8;
     e.c = 100;
     e.i = anzahl;
    
     return e;
    }
    

    Und dann in der main():

    int anzahl = 0, c = 0, i = 0;
    
    Eingabe e = dateieingabe(anzahl, c, i);
    
    cout << e.anzahl << "...";
    

    Alles ungetestet. Es geht um das Prinzip.



  • 1. An eine Funktion werden (wenn nicht anders angegeben) Kopien der Werte übergeben.
    Daher hast du in der Funktion keinen Einfluß auf die Quelle.

    2. Du mußt den Rückgabewert (wenn vorhanden) auch verwenden, z.B. einer Variablen zuweisen.
    Es gibt nur einen Rückgabewert, nicht drei oder so. (Eine struct ist aber auch ein Wert)

    3. Bei Arrays wird die Adresse als Kopie übergeben. Daher weiß die Funktion, wo die Originaldaten stehen und kann sie somit verändern. (Dieses Verhalten kann man aber auch für normale Variablen bekommen)

    int foo(int a, int b, int c)
    { b = b/2; //aendert nichts an den aufrufenden Werten
    
      return a + b * c; // Rückgabe
    }
    
    int main()
    { int wert = 3;
      int bar  = 4;
      int ergebnis;
    
      ergebnis = foo(1,bar,wert);
      cout << ergebnis <<'\n';
    
      cout << foo(bar, 10, wert/3); <<'\n';
    
    }
    


  • Ich antworte mal jedem Einzeln, um das ganze übersichtlich zu halten.

    @Wutz: Danke für deine Anmerkungen! Also alles was ich dazu sagen kann ist, dass mir das tatsächlich so vorgegeben wurde. Mein Dozent lehrt uns C++ und lässt uns mit Arrays arbeiten. Strukturen haben wir noch nicht behandelt und das Übungsblatt enthält den Hinweis, das alle Aufgaben mit den gegebenen Mitteln zu realisieren sind. Nichtsdestotrotz verstehe ich deine Erklärungen zu Strukturen wunderbar und wünschte mir, ich würde auf die Nutzung solcher meine Punkte bekommen 🙂
    Das ganze ist nun ohne Strukturen viel aufwendiger.

    @gfhf..
    Danke, auf diese Eigenschaft von Funktionen bin ich nun auch gestoßen. Du hast Recht, dass ich hinter der return-Anweisung mit mehreren Variablen und Kommas etwas anderes vermutet habe.
    Deinen Vorschlag mit Zeigern und Referenzen habe ich aufgefasst. Ich habe mich selber auch in der Zwischenzeit um eine Lösung bemüht und bin schon ein gutes Stück weiter! Dazu gleich am Ende des Beitrages mehr.

    @DirkB: Die allgemeinen Hinweise sind wirklich sehr nützlich fürs Verständnis. Funktionen und sie selbst als Werte (zugewiesen wie z.B. beispiel = dateneingabe(var) habe ich nun verstanden. Und durch deinen dritten Punkt weiß ich auch, warum Arrays im Prinzip noch einfacher sind. Das Wissen habe ich mir nun zunutze gemacht und habe es nun auch geschafft Arrays zu deklarieren, zu übergeben und "zurückzugeben" (wobei dies ja nun bei Zeigern und Referenzen nicht mehr direkt zutrifft).

    Ich habe nun also mit Zeigern und Referenzen gearbeitet um Variablen in einer aufgerufenen Funktion zu manipulieren und sie nach Abschluss der Funktion mit ihrem neuen Wert aufzurufen. Das hat alles wunderbar geklappt, sowohl mit einfachen int-Variablen als auch mit Arrays vom Typ double. Nun habe ich versucht dies in meinem ursprünglichen Quellcode so zu implementieren. Dabei versuche ich den Zeigervariablen vom Typ int und den Arrays (einer vom Typ int, Rest vom Typ double) nun Werte aus einer Textdatei zuzuweisen.
    Dabei muss es aber zu Konvertierungsproblemen kommen, denn die Werte werden bei Programmausführung nur teilweise richtig eingelesen.

    Ausgabe:
    http://fs5.directupload.net/images/160530/66ossqv5.png

    // Include-Anweisungen, std::namespace
    (...)
    
    // Prototypen
    void dateieingabe(int *anzahl, int *c, int *i, int Items[], double w[], double p[]);
    
    // Globale Konstante	
    const int MAXITEMS = 100;	// Maximale Anzahl Items
    
    // Funktionen
    void main(void)
    {
    
    // Lokale Variablen
    int anzahl,					// Anzahl an Items
        c,						// Kapazitätsrestriktion
        i;						// Itemindex 
    // Lokale Arrays
    int		Items[MAXITEMS];	// Nummern der Items
    double	w[MAXITEMS],		// Gewichte
    	    p[MAXITEMS];		// Profit, dessen Summe maximal werden soll
    
    dateieingabe(&anzahl, &c, &i, Items, w, p);
    
        _getch();
    }
    
    void dateieingabe(int *anzahl, int *c, int *i, int Items[], double w[], double p[])			
    {
    
    	// Lokaler Eingabe-Stream
    	ifstream	datei1;				// Beschreibt den Dateieinlese-Stream
    
    	// Char-Variablen
    	char		zeile[81];			// max. 80 Zeichen pro Zeile für Kommentare
    
    	// Benutzerdefinierte Textdatei öffnen
            (...)
    
    	datei1.open("input_items.txt");
    
    	// Zustand der Textdatei prüfen
            (...)
    
    	// Benutzerdefinierte Textdatei lesen
    	datei1.getline(zeile,81);						
    	cout << "\n" << zeile << endl;
    	datei1 >> *anzahl;									
    	cout << "Anzahl an Items: " << *anzahl << endl;
    	datei1.ignore();
    	datei1.getline(zeile,81);
    	cout << "\n" << zeile << endl;
    	datei1 >> *c;									
    	cout << "Kapazitaet des Rucksacks: " << *c << endl;
    	datei1.ignore();
    
    	datei1.getline(zeile,81);
    	cout << "\n" << zeile << endl;
    	for (*i = 1; *i <= *anzahl; *i++)
    	{
    		datei1 >> Items[*i-1] >> p[*i-1] >> w[*i-1];	
    
    		// Kontrollausgabe
    		cout << "\n" << Items[*i-1] << " ";
    		cout << fixed << setprecision(1) << p[*i-1] << " " << w[*i-1];
    
    		if (*i >= *anzahl)
    		{
    			cout << "\n\n" << "Alle Vektoren gefuellt. Anzahl der definierten Vektoren betraegt " << *anzahl;
    			cout << "\n\n\n" << "Naechster Schritt: Anwendung des Greedy-Algorithmus - Quotientenbildung";
    			cout << "\n\n";
    			_getch();
    
    		}
    	}
    
    }
    

    Die Input-Datei ist völlig in Ordnung, die funktionierte früher auch noch.

    Anzahl Items
    8
    Volumen
    102
    Itemindex/p/w
    1 	100. 20.
    2 	60. 30.
    3 	15. 30.
    4 	1. 	10.
    5 	15.	2.
    6	90.	20.
    7	40.	40.
    8	10.	60.
    

    Mein Ansatz wäre, dass es irgendwelche Probleme mit den Datentypen gibt, wobei diese allerdings (als die Variablen noch global definiert wurden) ihren Zweck richtig erfüllten. Ich muss irgendetwas offensichtliches übersehen. Womit kann das fehlerhafte Einlesen denn hier zusammen hängen? Der Code funktionierte früher ja ohne Probleme, nur dass ich diesmal Zeigervariablen verwende, statt "normale". Aber im Prinzip muss ich doch mit diesen innerhalb einer Funktion auch normal umgehen können. Das dass teilweise funktioniert zeigt ja auch die Ausgabe am Bildschirm.

    Ich hoffe, dass dies meine letzte Frage sein wird, da ich euch ja inzwischen schon sehr in Beschlag nehme hier 🙂 Wäre schade, wenn es jetzt daran scheitert!



  • Zunächst mal musst du nicht alle Variablen in der Paramterliste definieren.
    Laufvariablen von Schleifen haben da sehr, sehr selten etwas zu suchen.

    Dann solltest du auch bessere Namen wählen. c ist sowas von nichtssagend.
    Meist wird damit ein einzelner char benannt.

    i für die Laufvariable ist ok. Dann aber bitte lokal.
    Noch loakler. Die kannst du im der Schleifenkopf definieren.
    Und fang bei 0 an zu zählen.

    Zudem solltest du die maximale Größe der Arrays auch mit an die Funktion übergeben. (Wenn die gleich groß sind, reicht ein Wert)

    Zu deinem Fehler:
    Das *i++ in der for-Schleife macht nicht das, was du möchtest.
    Das verändert i (also den Zeiger) und nicht *i (den Wert, auf den i zeigt)

    for (int i = 0; i < *anzahl; i++)
    


  • Nochmal zu den Variablennamen:
    du schreibst

    double  w[MAXITEMS],        // Gewichte
            p[MAXITEMS];        // Profit, dessen Summe maximal werden soll
    

    Warum nennst du die Variablen nicht genau so, wie die Kommentare dahinter? Also:

    double weights[MAXITEMS];
    double profits[MAXITEMS];
    

    Dann kannst du da wohl auch auf den Kommentar verzichten, wenn die Variable gleich gut benannt ist.

    Und for-Schleifen so (beachte insbesondere das "int i" - das ist eine wirklich nur in der Schleife verfügbare Variable - du sollst hier nicht irgendein i von anderswo recyclen):

    for (int i = 0; i < anzahl; ++i){ ... }
    

    PS: Komischer Lehrer, der in C++ Arrays gleich am Anfang beibringt - auf Arrays kann man erstmal ganz verzichten, wenn man gleich vector<> lehren würde.



  • Vielen Dank DirkB!

    Ich nehme mir deine Anregungen zu Herzen. Das mit der sinnfreien Übergabe von Laufvariablen sehe ich ein, das habe ich nun zunächst mal lokal in der Funktion definiert. Die Initialisierung in Schleifen prüfe ich später dann nochmal, wenn das Programm an sich insgesamt ohne globale Variablen läuft.

    Außerdem überprüfe ich dann noch die Namen meiner Variablen. Solche Hinweise sind wirklich nützlich, ich arbeite bei wirtschaftswissenschaftlichen Rechnungen häufig mit "c" welche für irgendeine Kapazitätsrestriktion steht und habe das einfach so in den Code übernommen.

    Die maximale Größe der Arrays habe ich nun als globale Konstante im Programm - ich denke das ist absolut in Ordnung, da mit "const" definierte Variablen ohnehin unveränderlich sind, nachdem sie anfangs initialisiert wurden.
    Jedenfalls habe ich den Arrays nun mit "MAXITEMS" die maximale Größe beim Funktionsaufruf mitgegeben.

    Und vielen Dank, das war der Fehler! Wenn der Zeiger natürlich plötzlich in irgendwie geartete Werte springt kann das ja nicht funktionieren.

    Wunderbar! Ich denke, mit dem Wissen das ich hier mitnehmen konnte kann ich nun auch problemlos mit den nächsten zwei Funktionen fortfahren! Wenn ich den Programmablauf durchdenke sollte der Rest genau so abzuarbeiten sein.

    Nochmal vielen Dank für die Hilfe! Ich melde mich hier nur nochmal dann, wenn Fehler auftreten und ich diese mithilfe meiner Unterlagen oder mit Dokumentationen selber nicht nachvollziehen kann 😃

    Edit:
    @wob: Na da bin ich aber noch gespannt, wann das Thema "vector" im Unterricht aufkommt. Wenn ich lese, dass Arrays damit unnötig werden, dann joa. Mal sehen, wie er uns das dann auftischt.

    Wie ich oben schon zu Dirk geschrieben habe sollte ich mir wirklich noch Gedanken über klar definierte Variablennamen machen. Ich empfand die Nutzung von Variablennamen mit einem Buchstaben als "schick" und "schlank". Bevor ich diese Übungsaufgabe abgebe werde ich das nochmal ändern.

    Und gut, ihr habt mich auch von der Sinnhaftigkeit überzeugt Laufvariablen nur in Schleifen zu nutzen und nicht herum zu tragen. Wird geändert 🙂



  • PS: in deinem Fall natürlich *anzahl in der for-Schleife, aber eigentlich ist ein pointer auf ein int eher ungewöhnlich in dieser Situation.

    Und eine Funktion mit 6 (!) Parametern hat schon grenzwertig viele Parameter. Weniger ist oft mehr - bei vielen Parametern wird der Code schnell unübersichtlich. Aber gut, wenn ihr noch keine structs kennt...



  • Ja ich denke mal das ist nur so, weil der Dozent mit den Strukturen noch einen Moment abwarten möchte.

    Bei mir tauchte nun leider neue Fehlermeldung auf:

    1>Rucksack-Problem.obj : error LNK2001: Nicht aufgelöstes externes Symbol ""void __cdecl berechnung(int *)" (?berechnung@@YAXPAH@Z)".
    

    Ich habe doch außer die Include-Dateien nichts Externes eingebunden, wie kann es da dann zu so einem Fehler kommen? Im Internet stößt man wenn man nach diesem Fehler sucht nur auf Probleme in der objektbezogenen C++ Programmierung.

    Edit: Okay, habe den Fehler bereits behoben. Ich habe nochmal meinen Prototypen "berechnung", den Funktionsaufruf und die darin aufgerufenen Variablen überprüft und nochmal ergänzt. Auch, wenn ich nicht weiß warum, hat sich das Problem dadurch erübrigt.



  • Gibt es einen Grund warum du dich mit den Zeigern quälst? Stattdessen wären Referenzen hier wesentlich einfacher zu nutzen. Damit würdest du dir einige Dereferenzierungen sparen, da sich Referenzen wie "normale" Variablen verhalten.

    Noch ein Tipp: Du solltest beim Öffnen der Datei prüfen, ob ein Fehler aufgetreten ist und darauf reagieren.

    bool Dateneingabe()
    {
     std::ifstream ifs;
    
     ifs.open(...);
    
     if (!ifs.is_open())
      return false;
    
     // ...
    
     return true;
    }
    
    // In main() oder wo auch immer
    if (!Dateneingabe())
    {
     // Error
     return 1;
    }
    
    Weiterarbeiten();
    


  • Guten Morgen!

    Zu deiner Frage gfhf..., die Möglichkeit Referenzvariablen zu nutzen habe ich vor den Zeigern in Betracht gezogen. Leider erhalte ich beim kompilieren sofort einen kryptischen Fehler, den ich beim recherchieren nur bei Fragen zur objektbezogenen C++ Programmierung, z.B. Spieleprogrammierung, finde.

    error LNK2001: Nicht aufgelöstes externes Symbol void __cdecl ?@@YAXHHH@Z)".
    

    Dabei beziehe ich doch, außer bei den #Include-Dateien, keine externen Dateien mit ein. Deswegen habe ich mich dann mit Zeigern beschäftigt und das Programm gestern auch fertigstellen können, obwohl es nun derzeit durch die Übergabe von teilweise 6-7 Parametern beim Funktionsaufruf sehr unübersichtlich wird (ist aber nun leider die Vorgabe). Nun ja, das ist der Grund, warum ich keine Referenzvariablen genutzt habe: weil der Compiler dabei einfach rumspinnt.

    Ich kann mir vorstellen, dass ich in einer nächsten Übungsaufgabe diese Sachen dann wie hier mehrfach vorgeschlagen in Strukturen zusammenfassen soll.


Anmelden zum Antworten