Datenaustausch zwischen Funktionen (Greedy-Algorithmus)



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



  • Xalion schrieb:

    weil der Compiler dabei einfach rumspinnt.

    Quatsch.
    Der Compiler macht keine Fehler.
    Du machst die Fehler. Wenn du den Compiler nicht bedienen kannst, sprich - nicht programmieren kannst - kann der Compiler nichts dafür.
    Wenn du Unsinns-Lehrpraktiken deiner Deppen-Professoren das Wort redest, kannst du zwar letztendlich auch nichts dafür, lernst aber eben zuerst mal Unsinn.
    Und es ist schizophren etwas zu lernen, wovon man vorher schon weiß, dass das Unsinn ist. Und in dieser Unsinns-Lernphase rumzujammern ist dann bloß noch naiv.



  • Xalion schrieb:

    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.

    Vielleicht kannst du ja ein kleines Beispielprogramm zeigen, dass diesen Fehler aber noch beinhält. Mit Zeigern wirst du langfristig aufjedenfall Fehler machen. Insofern sollte man es vermeiden damit zu hantieren, wenn es nicht nötig ist.


  • Mod

    Und es ist schizophren etwas zu lernen, wovon man vorher schon weiß, dass das Unsinn ist.

    Nein. Es ist (leider) nötig. Weil nur so der Deppen-Prof die Punkte vergibt.

    Idealerweise lernt man den Unsinn, und behält klar im Hinterkopf "das ist Unsinn", während man die richtigen Methoden lernt.


Anmelden zum Antworten