Autor Beitrag
nav93
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 41



BeitragVerfasst: So 25.12.16 20:48 
Hallo Leute,

ich bereite mich gerade auf die c++ klausur vor . Bei uns besteht sie darin Bildschirmausgaben aus einem Quellcode zu notieren. Leider hänge ich bei vollgenden Code :

ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
#include <iostream>
#include <string>

using namespace std;

double ff(double ar[],int n)
{ cout << "ff" << endl;
  if (n == 1) return ar[0];
  double m = ff(ar, n-1);
  if (ar[n-1] > m) return ar[n-1];
  else return m;
  
}

void main()
{
  double
    x[8] = {-3.4,-2.0,-9.9,-3.0,-0.5,-6.6,-1.9,-0.21 };

  cout << ff(x, 8) << endl;
  system("Pause");
}


also die ff sind eigentlich recht verständlich . das für jedes feld einmal ff ausgeben wird. jedoch versteh ich nicht wieso die -0,21 am ende ausgegeben wird. Welchen wert würde die Variable m haben ? würde mich freuen wenn mir jemand sagen kann wie ich bei so einer aufgabe am besten voran komm !

liebe grüße
C#
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Mo 26.12.16 01:02 
Hey,

für jede Zahl in dem Array wird - wie du schon richtig festgestellt hast - ein "ff" in den Outputstream geschrieben. Außerdem hat die Funktion ff ja selbst auch einen Rückgabewert vom Typ double, d.h. dass auf jeden Fall noch eine Zahl in den Ausgabestream geschrieben wird (weil der Rückgabewert ja auch in den Ausgabestream geschrieben wird: cout << ff(x, 8) << endl;).

Warum wird -0.21 ausgegeben?

Wenn du dir die Funktion ff anschaust, siehst du, dass sie eine lineare Rekursion enthält. Diese kürzt das Array jedes mal um ein Element, bis es nur noch ein Element besitzt. Wenn wir uns nun einen Stack aus den Argumenten bauen sieht der so aus (es sind nicht wirklich die Funktionsargumente, sondern deren effektive Werte bzw. Bedeutungen):
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
              arr[0...n-1]
-3.4, -2.0, -9.9, -3.0, -0.5, -6.6, -1.9, -0.21  // 1. Aufruf von main()
-3.4, -2.0, -9.9, -3.0, -0.5, -6.6, -1.9         // 2. Auruf von ff(...)
-3.4, -2.0, -9.9, -3.0, -0.5, -6.6               // 3. Auruf von ff(...)
-3.4, -2.0, -9.9, -3.0, -0.5                     // 4. Auruf von ff(...)
-3.4, -2.0, -9.9, -3.0                           // 5. Auruf von ff(...)
-3.4, -2.0, -9.9                                 // 6. Auruf von ff(...)
-3.4, -2.0                                       // 7. Auruf von ff(...)
-3.4                                             // 8. Auruf von ff(...)


Nun sehen wir uns die erste if-Bedingung von ff() an:
ausblenden C#-Quelltext
1:
if (n == 1return ar[0];					

Im 8. Aufruf von ff() ist nur noch eine Zahl in dem Array (bzw. n wurde so lange verkleinert), wodurch die Bedingung n == 1 erfüllt ist. Ab hier löst sich der Stack wieder auf.
Der Rest von ff() tut folgendes:
1. lege das Ergebnis des letzten rekursiven Aufrufs in der Variable m ab.
2. vergleiche die letzte Zahl im Array mit m
3. ist die letzte Zahl im Array größer als m, gib diese zurück. Andernfalls gib m zurück.

Kurz gesagt: es wird immer die größte Zahl zurück gegeben.

Sieht man sich diese Bedingungen im Stack an sehen sie so aus (arr[n-1] entspricht immer der letzten Zahl im Array):
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
arr[n-1]       m         return

-3.4                      -3.4        // 8. Aufruf von ff(...)
-2.0    >    -3.4   -->   -2.0        // 7. Aufruf von ff(...)
-9.9    >    -2.0   -->   -2.0        // 6. Aufruf von ff(...)
-3.0    >    -2.0   -->   -2.0        // 5. Aufruf von ff(...)
-0.5    >    -2.0   -->   -0.5        // 4. Aufruf von ff(...)
-6.6    >    -0.5   -->   -0.5        // 3. Aufruf von ff(...)
-1.9    >    -0.5   -->   -0.5        // 2. Aufruf von ff(...)
-0.21   >    -0.5   -->   -0.21       // 1. Aufruf von main()


Da alle Zahlen negativ sind, ist -0.21 die größte Zahl im Array. Du siehst auch bei "1. Auruf von main()" (letzte Zeile im Stack oben) den Rückgabewert -0.21. Die -0.21 wird dann zuletzt an cout weiter gegeben.
So würde ich immer bei der Analyse von rekursiven Funktionen vorgehen (wenn ich nur Stift und Papier als Hilfsmittel habe natürlich ;)). Einfach mal versuchen die Bedingungen für die return-Sprünge zu verstehen und dann einen Stack daraus basteln (angefangen mit dem tiefsten rekursiven Aufruf).

Hoffe es ist verständlich.

Frohe Weihnachten

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler

Für diesen Beitrag haben gedankt: nav93
nav93 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 41



BeitragVerfasst: Mo 26.12.16 14:32 
Hallo,

vielen dank für deine wirklich sehr sehr sehr gute und verständliche erklärung . Eine Frage hätte ich jedoch noch !

Hier deklarieren wir ja m :

ausblenden C#-Quelltext
1:
double m = ff(ar, n-1);					


und hier wird verglichen :

ausblenden C#-Quelltext
1:
2:
if (ar[n-1] > m) return ar[n-1];
else return m;


was ist denn hier der unterschied ? ich habe gedacht, dass bei m (ar, n-1 ) das selbe ist wie ar[n-1].
ist m dann quasi der rückgabe wert? hab das mit dem m nicht so richtig verstanden :D sorry ^^

dir und euch allen natürlich auch frohe weihnachten !
C#
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Mo 26.12.16 15:53 
m speichert den Rückgabewert des vorherigen Aufrufs. Es ist nur dann der Rückgabewert, wenn m größer ist als die letzte Zahl in ar.
ff(ar, n-1) ist nicht gleich ar[n-1]!
stack
In diesem Bild siehst du sämtliche Werte und Infos in Form eines Stacks. In der ersten Spalte steht n (der 2. Parameter von ff(...)). In Spalte zwei siehst du ar, wie es von ff(...) in diesem Aufruf interpretiert wird (tatsächlich ist ar immer -3.4 aber das ist hier egal und verwirrt nur). Spalte drei und vier zeigen dir den Vergleich zwischen m und ar[n-1] (wie du siehst sind sie nicht identisch). Spalte fünf listet die jeweils verwendeten return-Anweisungen auf. Die vorletzte Spalte zeigt dir, welcher Wert tatsächlich zurück gegeben wird und in der letzten Spalte steht die Reihenfolge. Sie gibt die Position im Stack an, und wer die Methode aufgerufen hat.

// Nachtrag:
Es hilft auch oft (zur nachträglichen Analyse) den Code mal in Visual Studio step by step zu debuggen. Dann hast du einen Callstack und alle Werte der Variablen in jedem rekursiven Aufruf.
Einloggen, um Attachments anzusehen!
_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler

Für diesen Beitrag haben gedankt: nav93
nav93 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 41



BeitragVerfasst: Mo 26.12.16 18:35 
woooooow vielen dank jetzt habe ich es verstanden . ich tue schritt für schritt debuggen aber irgendwie wurde mir das nicht angezeigt. meinst du mit f10 oder f11 debuggen ?
C#
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Mo 26.12.16 19:15 
Kommt drauf an was du debuggen willst und wo du den ersten Breakpoint hast.

Mit F10 springst du immer in die nächste Zeile. D.h. der Code in der Zeile wird komplett ausgeführt und dann wird wieder unterbrochen. Das nennt VS Prozedurschritt.
Mit F11 machst du Einzelschritte. Der Debugger hält dann bei jeder Operation an. Wenn du z.B. in einer Zeile stehst wie hier:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
#include <iostream>
#include <string>

using namespace std;

double ff(double ar[],int n)
{ cout << "ff" << endl;
  if (n == 1return ar[0];
  double m = ff(ar, n-1);
  if (ar[n-1] > m) return ar[n-1];
  else return m;
  
}

void main()
{
  double
    x[8] = {-3.4,-2.0,-9.9,-3.0,-0.5,-6.6,-1.9,-0.21 };

  cout << ff(x, 8) << endl;
  system("Pause");
}

dann landest du mit F10 in der nächsten Zeile bei system("Pause"), während du mit F11 in die Funktion ff(...) springst (je nach Einstellung landest du dann noch in dem stream operator << von ostream).

Bei C++ unterbricht der mit F11 ziemlich oft. Ich würde einfach einen Breakpoint am Anfang von ff(...) setzen, dann Debugging normal starten (F5) und dann ab dem Breakpoint mit F10 durchgehen.

// Nachtrag
Wundere dich nicht, wenn dir bei der Variable ar in der Funktion ff(...) im Debugger nur -3.4 angezeigt wird. Wenn du einen Array als Parameter übergibst, wird immer ein Pointer übergeben.
ausblenden C#-Quelltext
1:
double ar[]					

ist gleichbedeutend mit
ausblenden C#-Quelltext
1:
double* ar					

was wiederum nur ein Pointer auf die erste Zahl im Array ist. Du kannst dir mit einem kleinen Trick aber das ganze Array anzeigen lassen, indem du im Debug Modus das Observer Window öffnest. Gehe dazu unter Debuggen > Fenster > Überwachen > Überwachen 1. Dann geht ein Fenster auf, indem du die erste Zeile eintippst (dazu muss der Debugger innerhalb von ff(...) angehalten werden):
window
Einloggen, um Attachments anzusehen!
_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler

Für diesen Beitrag haben gedankt: nav93
nav93 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 41



BeitragVerfasst: Mo 26.12.16 20:17 
vielen Dank !

du hast mir echt weitergeholfen mit den Tipps!
nav93 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 41



BeitragVerfasst: Di 27.12.16 14:58 
hey,

hab eventuell noch eine frage zu einem Programm. Eigentlich verstehe ich da alles aber eine Sache macht mich etwas nachdänklich .

das Programm an sich :

ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
#include <iostream>
using namespace std;

class B {
public
  B(int a = 0int b = 1) :z(a), n(b)
  {
    cout << "K1" << endl;
    static int incz = 0;
    static int incn = 0;
    z = z + incz;
    n = n + incn;
    incz++;
    incn++;
  }

  B(const B& r) : z(r.z), n(r.n)
  {cout << "KZ" << endl;}
  void print() { cout << z << '/' << n << endl; }
  private
    int z, n;
};





B cf(B r) {
  B s=r;
  return s;

}

int main() {
  B w;
  B x(227);
  B y(58);
  B z(x);
  B u = cf(y);
  w.print();
  x.print();
  y.print();
  z.print();
  u.print();
  
  
}


Die Variablen incz++ & incn++ sollten doch wenn man in die main geht mit 1 Anfangen oder ?

quasi hier :

ausblenden C#-Quelltext
1:
2:
int main() {
  B w; // hier sollte doch incz++ und incn++ den wert 1 haben


wenn ich das selbe programm mit 10 und rückwärts laufend Ausgebe, dann ist der Wert auch direkt auf 9 gesetzt . Jedoch ist das hier anders ? oder hab ich einen denkfehler

vielen dank schon mal
C#
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Di 27.12.16 17:30 
Sorry aber ich verstehe deine Frage nicht. Die Variablen incz und incn fangen erst mal bei 0 an. Sobald dann der erste Konstruktor von B aufgerufen wurde haben sie den Wert 1 und dieser erhöht sich mit jedem Aufruf des Standardkonstruktors von B um 1.

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
nav93 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 41



BeitragVerfasst: Mi 28.12.16 14:46 
genau das war meine Frage ! aber wenn ich das programm kompiliere beginnt es nicht mit 1 sondern mit 0 :O
die erste print ausgabe also x.print müsste ja sein 1/2 laut deiner aussage. aber es kommt 0/1
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mi 28.12.16 16:20 
Du setzt doch erst die Membervariable z und erst danach erhöhst du incz und incn. :?:

Und mittels Debugging solltest du das doch recht schnell selbst durchlaufen können...