6. Grundlagen von Funktionen C++ Programm Bestandteile eines C++ - Programms C++ Kern vordefinierte Datentypen, Operatoren, Befehle Standardbibliothek vordefinierte Funktionen (und Klassen) Anwender Selbsterstellte Funktionen (und Klassen) Sonstiges externe Bibliotheken Dr. Norbert Spangler /Informatik 21.11.2012 2 Beispiele für mathematische Funktionen while ( abs (x*x-a) >=1.0e-10 ); -> abs x1= sqrt( b*b - 4*a*c); -> sqrt y = cos(x*pi); -> cos usw. Diese mathematischen Funktionen werden neben zahlreichen anderen in der Bibliothek cmath zur Verfügung gestellt. Erforderlich #include <cmath> Dr. Norbert Spangler /Informatik (vgl. Musterprogramm) 21.11.2012 3 Beispiele für andere Funktionen Suchen eines Elements in einem Array erforderlich: Arrayelemente + Anzahl +Suchelement Ergebnis=Funktionswert: gefundener Index (Stelle) stelle = suche(array,anzahl,suchelement) Sortieren der Elemente eines Arrays erforderlich: Arrayelemente + Anzahl Funktionswert ? Ergebnis: sortierter Array sortiere(array,anzahl) Formatierte Ausgabe eines Arrays (einer Matrix) am Bildschirm erforderlich: Arrayelemente + Anzahl Funktionswert ? Ergebnis: Bildschirmanzeige anzeige(array,anzahl) Regel: Funktionen machen keine Ausgaben, es sei denn es handelt sich um eine Ausgabefunktion Dr. Norbert Spangler /Informatik 21.11.2012 4 Motivation Funktionen lösen spezielle (Teil-)Aufgaben mathematische Formeln spezielle Algorithmen: Suchen, Sortieren, Gleichungen, Auswertungen Ein-/Ausgabe für Tastatur, Bildschirm, Dateien usw. Steuerungen von Geräten, Anlagen,… Funktionen sind damit eine Zusammenfassung von Arbeitspaketen und bieten eine Möglichkeit zur Verteilung des Programmieraufwands auf Teams Voraussetzung Die Schnittstellen der Funktionen untereinander sind klar festgelegt bzw. beschrieben. Dr. Norbert Spangler /Programmieren 21.11.2012 5 Motivation Funktionen sind damit "selbstständige" Programmteile innerhalb einer Anwendung zur -Strukturierung/Beherrschung der Komplexität (Komponenten) - Verbesserung des Testens - Lösung von Teilaufgaben - Verbesserung der Übersichtlichkeit und Lesbarkeit - Wiederverwendung (z. Bsp. Ein-/Ausgabe von Arrays) - Arbeitsorganisation d.h. Aufteilung auf Teams - Übersetzung: jede Funktion wird durch einen eigenen Compilerlauf bearbeitet Damit könnten auch unterschiedliche Programmiersprachen verwendet werden - Archivierung in Bibliotheken Funktionen erhöhen die Programmqualität Dr. Norbert Spangler /Programmieren 21.11.2012 6 Beispiel / Musterprogramm Wurzel berechnen Wurzel berechnen Eingabe a Eingabe a a<=0 a <= 0 Ja a muss >0 sein ja Nein nein a muss > 0 sein Ausgabe wurzel(a) x=a Solangex*x-a ungenau x*x-a x = x - ------2x Wurzel(a) x=a Ausgabe x Solangex*x-a ungenau Das große "unübersichtliche" Struktogramm wird ersetzt durch mehrere "kleine" übersichtliche für jede Funktion - Eingabe mit Plausibilitätskontrolle - „eigentliche“ Aufgabe Dr. Norbert Spangler /Informatik x*x-a x = x - ------2x Ergebnis x 21.11.2012 7 main mit Verwendung einer Funktion wurzel // Wurzel berechnen #include <iostream> using namespace std; void main() { double radikand; double wurzel(double); cout <<"\n radikand eingeben "; cin>>radikand; //Plausibilitaetskontrolle if ( radikand<0 )// unzulaessige Eingabe cout <<"\n darf nicht < 0 sein "; else //Ergebnis cout <<"\n Wurzel von "<<radikand <<" = "<< wurzel(radikand) <<endl; } Dr. Norbert Spangler /Informatik Funktion1: main Prototyp Dadurch ist wurzel in main bekannt Aufruf hier wird wurzel verwendet aktuelle Parameter mit diesen Daten aus dem main soll wurzel arbeiten 21.11.2012 8 Die Funktion wurzel formale Parameter (Platzhalter) double wurzel(double a) { const double genauigkeit=0.001; double x=a; if (a==0.0) return 0.0; else while ( abs (x*x-a)>genauigkeit ) x=x-(x*x-a)/(2*x); return x; } Funktionskopf Definition Funktionsblock Ergebnis (Funktionswert) Dr. Norbert Spangler /Informatik 21.11.2012 9 Dateien Üblicherweise sollte man jede Funktion innerhalb eines Projekts in einer eigenen Datei speichern. Anmerkung: es geht auch mit einer Datei. main Funktion in Datei main.cpp in einer Datei func.cpp Compiler erzeugt main.obj Compiler erzeugt func.obj Linker projekt.exe-Datei Dr. Norbert Spangler /Informatik 21.11.2012 10 Verallgemeinerung Situation: main (ist eine benutzerdefinierte Funktion); sie verwendet wurzel (ist eine weitere benutzerdefinierte Funktion), diese verwendet abs (ist eine bereitgestellte Funktion) Allgemein: Eine Funktion1 verwendet eine andere Funktion2 Dr. Norbert Spangler /Informatik 21.11.2012 11 6.1 Aufgabenstellung/Anforderungen funktion1 verwendet funktion2 z.B. funktion1 geschrieben von Programmierer a funktion2 geschrieben von Programmierer b Wie erkennt funktion1 dass es funktion2 gibt? Was passiert wenn funktion2 nicht zur Verfügung steht? Wie kann funktion1 weiter programmiert werden wenn funktion2 noch nicht verfügbar ist? Kann funktion2 programmiert/getestet werden wenn funktion1 noch nicht existiert? Wie erfährt funktion2, mit welchen Daten aus funktion1 sie arbeiten muss? Wie wird das Ergebnis von funktion2 an funktion1 übermittelt? Bei Mehrfachverwendung von funktion2 in funktion1: An welcher Stelle in funktion1 geht es weiter? Dr. Norbert Spangler /Informatik 21.11.2012 12 Schnittstellen funktion1(main) – funktion2 (f) main f(t) y=f(x); Programmcode Programmcode Sprung Sprung Rücksprung Stelle 1 z=f(h-2)+1; Rücksprung Stelle 2 x y h z Variante 2: Adresse verraten t Daten Daten return Variante 1: Wert ausrechnen kopieren Dr. Norbert Spangler /Programmieren 21.11.2012 13 Erfüllung der Anforderungen/1 Wie erkennt funktion1 dass es funktion2 gibt? Ein Hinweis ist erforderlich: Prototyp Was passiert wenn funktion2 nicht zur Verfügung steht? Fehlermeldung des Linkers Wie kann funktion1 programmiert werden wenn funktion2 nicht verfügbar ist? Man erstellt eine einfache Vorabversion von funktion2 ohne wesentliche Funktionalität: Stub Kann funktion2 programmiert/getestet werden wenn funktion1 nicht existiert? Man erstellt ein separates Testprogramm für funktion2: Treiber Dr. Norbert Spangler /Programmieren 21.11.2012 14 Erfüllung der Anforderungen/2 Wie erfährt funktion2, mit welchen Daten aus funktion1 sie arbeiten muss? Bei der Verwendung der Funktion (Aufruf) müssen diese übermittelt werden. Hierzu gibt es unterschiedliche Techniken (Kopien/Adressen). Wie wird das Ergebnis von funktion2 an funktion1 übermittelt? Hierzu gibt es unterschiedliche Techniken. Die einfachste Variante ist mittels des Returnbefehls: return Funktionswert; Bei Mehrfachverwendung von Funktion2: An welcher Stelle in funktion1 geht es weiter? Die Stelle muss beim Wechsel vom Programmcode von funktion1 in den von funktion2 gemerkt und bei der Rückkehr verwendet werden. Dies wird automatisch erledigt. Dr. Norbert Spangler /Informatik 21.11.2012 15 Weitere Aspekte Eine Funktion hat einen Wert (Typ) Prinzipiell ist jeder Typ zulässig, also auch int, short,char,…. Hat eine Funktion "keinen Wert" wird als Typ void verwendet. Funktionen vom Typ void werden direkt aufgerufen Beispiel : tausche(x,y); sortiere(n,x); Funktionen mit Typ erscheinen als R-Wert Beispiel: w=wurzel(radikand) ; cout<<wurzel(b*b-4*a*c) <<endl; Eine Funktion hat kann beliebig viele Parameter haben Parameter werden durch Komma getrennt Beispiel wurzel(a,genauigkeit) allgemein funktionsname(parameter1,parameter2,.....,parameter_n) aber auch void main() //ohne Parameter Dr. Norbert Spangler /Informatik 21.11.2012 16 6.2 Programmierung von Funktionen Die Funktion sollte sinnvollerweise in einer eigenen cpp-Datei im Projekt stehen. Daher ist zu main eine weitere cpp-Datei hinzuzufügen. Der Name kann beliebig gewählt werden, üblicherweise Funktionsname.cpp. Diese enthält dann die Definition der Funktion. Je nach Größe des Projekts bzw. Anzahl oder Umfang der Funktionen kann man auch mehrere Funktionen zu einer Funktionsgruppe in einer Datei zusammenfassen. Ziel sollte generell die Übersichtlichkeit des Gesamtprogramms sein. Dr. Norbert Spangler /Informatik 21.11.2012 17 Funktionsdefinition Angabe von Funktionskopf Typ der Funktion und entspricht dem Typ des return-Wertes Name der Funktion wie ein Variablenname Deklarationsliste Typ und Name der Parameter genauer: formale Parameter (Platzhalter) Funktionsblock { hier steht was die Funktion macht } im Funktionsblock wird der Wert der Funktion ermittelt und durch return wert; beendet und dem aufrufenden Programm das Ergebnis (wert) dann übergeben Ist die Funktion vom Typ void, so wird sie durch return; beendet. Dr. Norbert Spangler /Informatik 21.11.2012 18 Definition/Beispiel #include<cmath> using namespace std; double wurzel(double a) { const double genauigkeit=0.001; double x=a; if (a==0.0) return 0.0; else while ( abs (x*x-a)>genauigkeit ) x=x-(x*x-a)/(2*x); return x; } Dr. Norbert Spangler /Informatik Wird hier benötigt wegen abs Achtung: Alle hier vorkommenden Größen sind lokale Größen der Funktion a genauigkeit x Ist die Funktion vom Typ void darf mittels return kein Wert zurückgegeben werden (also nur return; ). 21.11.2012 19 Funktionskopf Mit dem Funktionskopf beginnt die Definition der Funktion. Anzugeben sind Typ des Ergebnisses der Funktion Name der Funktion (wie Variablennamen) Liste der formalen Parameter in Klammern() Parametertyp1 Parametername1,Parametertyp2 Parametername2,…. typfunktion name(typ1 p1,…,typn pn) Beispiele: double wurzel(double a) //1 Parameter double wurzel(double a, double genauigkeit) //2 Parameter double loesung1(double a, double b, double c) //quadratische Gleichung - 3 Par. int zufallszahl() //kein Parameter- ganzzahlige Zufallszahl void anzeigesortiert(int a,int b, int c) // Typ der Funktion void Dr. Norbert Spangler /Informatik 21.11.2012 20 Funktionsblock Der Funktionsblock folgt auf den Funktionskopf innerhalb eines Paares geschweifter Klammern. Er kann alle bekannten Befehle enthalten. Insbesondere ist es möglich, neu benötigte Größen zu deklarieren. Diese sind dann lokale Größen in diesem Block und nach Verlassen des Blocks, d.h. nach Beendigung der Funktion, nicht mehr existent. Die formalen Parameter im Funktionskopf sind Platzhalter für die zu verwendenden Daten aus der aufrufenden Funktion. Sie sind ebenfalls lokale Variable. Eine Funktion wird mittels return beendet. Hierbei gibt es 2 Varianten. Dr. Norbert Spangler /Informatik 21.11.2012 21 Beendigung der Funktion/1 Die Funktion ist vom Typ void ( z.B. void main() ) Ein return ohne zusätzliche Angaben ist erforderlich also z.B. if ( a< 0 ) { cout<<"unzulaessige Eingabe"; return; } Ein return am Ende des Funktionsblocks, d.h. am Ende der Funktion kann man auch weglassen. Dr. Norbert Spangler /Informatik 21.11.2012 22 Beendigung der Funktion/2 Die Funktion ist nicht vom Typ void z.B. double wurzel(double a) Die Funktion muss mit return wert; beendet werden. Dabei ist wert ein Ausdruck vom selben Typ wie die Funktion also etwa im Falle des Typs double beispielsweise return 0.0; //Konstante vom Typ double return x; // Variable vom Typ double return x*x-1; //Ausdruck vom Typ double Dr. Norbert Spangler /Informatik 21.11.2012 23 6.3 Verwendung einer Funktion Will man in einer funktion1 (z.B. main) eine andere funktion2 (z.B. wurzel oder abs) verwenden, so muss man sie im Falle von Standardfunktionen mittels include bereitstellen bzw. im Falle eigenentwickelter Funktionen dem Compiler zur Plausibilitätskontrolle alle relevanten Informationen mitteilen wie - Was ist der Name der Funktion - Welchen Typ hat das Ergebnis? - Wieviele Parameter hat die Funktion? - Welchen Typ haben diese Parameter? Dies geschieht durch die Angabe des Prototyps. Dr. Norbert Spangler /Informatik 21.11.2012 24 Prototyp (Deklaration) Der Prototyp ist ähnlich dem Funktionskopf in der Definition. Es kann auf die Angabe der Namen der Parameter verzichtet werden. Der Typ ist jedoch erforderlich. Der Prototyp muss vor der ersten Verwendung angegeben sein. Form: typderfunktion namederfunktion(parametertyp1,…,parametertypn); Beispiel double wurzel(double) void main() { double radikand; double wurzel(double); … } Dr. Norbert Spangler /Informatik 21.11.2012 25 Aufruf der Funktion Beim Aufruf der Funktion müssen die (formalen) Parameter durch die aktuell zu verwendenden Parameter ersetzt werden. void main() { double radikand; double wurzel(double); cout <<"\n radikand eingeben "; cin>>radikand; //Plausibilitaetskontrolle if ( radikand<0 )// unzulaessige Eingabe cout <<"\n darf nicht < 0 sein "; else //Ergebnis cout <<"\n Wurzel von "<<a<<" = " << wurzel(radikand) <<endl; } Dr. Norbert Spangler /Informatik Der aktuelle Parameter für die Funktion wurzel ist hier radikand, eine Variable aus dem main!!! Hier darf auch ein ausdruck stehen wie wurzel (b*b-4*a*c) Der Ausdruck wird zuerst berechnet und das Ergebnis dann als Parameter verwendet. 21.11.2012 26 Aufruf der Funktion Ist die Funktion vom Typ void, so erfolgt der Aufruf direkt Beispiel: Funktion tausche zum Tausch zweier Variabler: … tausche(x,y);//die Werte von x und y sollen vertauscht werden … Hat die Funktion einen Typ, so muss sie sinvollerweise wie ein R-Wert behandelt werden: Beispiele: loesung1=(-b+wurzel(b*b-4*a*c)/(2*a); testergebnis=wurzel(x*x); cout<<" ergebnis "<<wurzel(x+1)<<endl; Dr. Norbert Spangler /Informatik 21.11.2012 27 Prinzipieller Ablauf des Aufrufs main Aufruf, Rücksprungadresse merken wurzel(b*b-4*a*c); Aktuelle Parameter auswerten und auf formale Parameter kopieren wurzel( double a ); Funktionsblock ausführen (lokale Objekte : a, genauigkeit, x) Rücksprungadresse holen, Wert mittels return übergeben main Dr. Norbert Spangler /Informatik 21.11.2012 28 Parameterübergabe Beim Aufruf werden die aktuellen Parameter ausgewertet (Ausdrücke) (lokaler) Speicherplatz wird bereitgestellt für formale Parameter, lokale Variable, Rücksprungadresse Die Werte der aktuellen Parameter werden auf die formalen Parameter kopiert Technik der Parameterübergabe : Call by Value Die Funktion arbeitet also immer mit Kopien und nie mit Variablen aus der aufrufenden Funktion. Damit können die aktuellen Parameter aus der aufrufenden Funktion nicht geändert werden (da nicht bekannt)! Dr. Norbert Spangler /Informatik 21.11.2012 29 Parameterübergabe Vorteil Ausdrücke werden nur ein mal ausgewertet, wenn der Parameter häufig in der Funktion verwendet wird. Parameter können nicht versehentlich geändert werden (Überschreiben von „Konstanten“) Nachteil Parameter mit hohem Speicherplatzbedarf werden dupliziert !!! Dr. Norbert Spangler /Informatik 21.11.2012 30 Beispiel void main() { double wurzel(double); … double w1=wurzel(b*b-4*a*c); double w2=wurzel(b*b+4*a*c); … } Aufruf von wurzel in main: double wurzel(double a) { const double genauigkeit=0.001; double x=a; if (a==0.0) return 0.0; else while ( abs (x*x-a)>genauigkeit ) x=x-(x*x-a)/(2*x); return x; } Ausdruck ( z.B. b*b-4*a*c )auswerten Ergebnis nach a von wurzel kopieren !!!! ->Achtung: a gibt es sowohl im main als auch in wurzel! Stelle für Rücksprung merken nach wurzel springen Ergebnis x berechnen Nach Rücksprungstelle springen und x übergeben(nach w1 bzw. w2 speichern) Weiter in main Dr. Norbert Spangler /Programmieren 21.11.2012 31 6.4 Arrays als Parameter Call by Value als Übergabetechnik kommt nur bei Variablen zum Einsatz. Das Anlegen von "Kopien" ist da akzeptabel. Bei Arrays ist es dagegen weniger sinnvoll. Daher wird in diesem Fall der Funktion die Anfangsadresse des Arrays in der aufrufenden Funktion übermittelt. Damit kann die Funktion mit den Originaldaten arbeiten und benötigt keine Kopien. Dr. Norbert Spangler /Informatik 21.11.2012 32 Beispiele/Funktionen void arrayin ( int n, char p_x[]) // Definition {//Array lesen for (int i=0;i<n;i++) cin>>p_x[i]; } void arrayout ( int n, char p_x[]) {//Array ausgeben cout<<endl; for (int i=0;i<n;i++) cout<<p_x[i]; cout<<endl; } void arraycopy ( int n, char p_x[],char p_y[]) {//Array kopieren for (int i=0;i<n;i++) p_y[i]=p_x[i]; Mehrere Arrays } sind möglich Dr. Norbert Spangler /Programmieren Durch Angabe der Indexklammern [ ] erkennt der Compiler, dass der entsprechende Parameter ein Array ist. Beim Aufruf der Funktion wird somit die Anfangsadresse des aktuellen Parameters übergeben. Durch den Typ wird die Länge der Arrayelemente festgelegt. Die Anzahl er Elemente wird benötigt. Die Elemente können damit geändert werden - Keine Kopie !!!!! 21.11.2012 33 Beispiel/main void main() { char x[10],y[10]; void arrayin( int,char[]); // Prototypen void arraycopy( int,char[],char[]); void arrayout( int,char[]); cout<<"Anzahl der Elemente "; int n; cin>>n; arrayin(n,x); // Aufrufe arraycopy(n,x,y); arrayout(n,y); } Durch Verwendung der Indexklammern [ ] erkennt der Compiler, dass der entsprechende Parameter ein Array ist. Aktueller Parameter ist jeweils nur der Name des Arrays ohne Indexklammern Dr. Norbert Spangler /Informatik 21.11.2012 34 Eindimensionale Arrays als Parameter Prototyp: typfunktion funktionsname( …, typarray[] , ….); -> im Prototyp ist nach dem Typ eine [] anzugeben bei Arrays In der Regel ist ein zusätzlicher Parameter vom Typ int für die Anzahl der Elemente notwendig. Beispiele: void arrayin( int,char[]); double summe(int,double[]); //Summation eines Arrays vom Typ double int summe(int,int[]); //Summation eines Arrays vom Typ int Aufruf: : funktionsname( …, namearray , ….); -> im Aufruf ist nur der Name des Arrays ohne [] anzugeben. Die Anzahl der verwendeten Komponenten steht in dem zusätzlichen Parameter. Beispiele s=summe(n,messwerte); // Aufruf mit Rückgabewert (R-Wert!!!) arrayin(n,x); // Aufruf einer Funktion vom Typ void Dr. Norbert Spangler /Informatik 21.11.2012 35 Eindimensionale Arrays als Parameter // Definition typfunktion namefunktion( …, int anzahl,typarray namearray[],…) {} In der Definition ist auf der entsprechenden Parameterposition der Name des formalen Parameters mit den Indexklammern [] anzugeben. Zusätzlich hat man in der Regel einen Parameter vom Typ int für die Anzahl der Elemente. Beispiel: void arrayin ( int n, char p_x[]) {//Array lesen for (int i=0;i<n;i++) cin>>p_x[i]; } Dr. Norbert Spangler /Informatik 21.11.2012 36 Mehrdimensionale Arrays als Parameter Bei mehrdimensionalen Arrays müssten bis auf die erste Dimension alle anderen angebenen werden. Dies betrifft Prototyp und Definition. Nur so kann die "Länge" des Elements und damit die Adressierung sichergestellt werden. Prototyp void verarbeitematrix (…, double [][10],…) void funktion(…,int [][31][24],…) Definition void verarbeitematrix (…, double mat [][10],…) {…}; void funktion(…,int datenarray[][31][24],…) {…}; Beim Aufruf dagegen ist wie bisher nur der Name des Arrays anzugeben: verarbeitematrix (mat,n,m); funktion(dat,k,l,m); Dr. Norbert Spangler /Informatik 21.11.2012 37 Mehrdimensionale Arrays als Parameter/Beispiel void mataus(int m,int n,int mat[][4]) { for ( int i=0;i<m;i++ ) { cout<<endl; for ( int j=0;j<n;j++ ) cout<<setw(3)<<mat[i][j]; } cout<<endl; } void main() { int zahl[5][4]={ {1,2,3},{4,5,6},{7,8,9},{10,11,12}}; void mataus(int,int,int[][4]); mataus(4,3,zahl);//Ausgabe von 4 Zeilen/3 Spalten mataus(5,5,zahl);//!!!!! } Dr. Norbert Spangler /Informatik Nicht deklarierte Elemente werden mit 0 initialisiert. Der Zugriff auf Elemente ausserhalb des Arrays wird nicht abgefangen 21.11.2012 38 6.5 Signatur Aufgabe Es soll die Wurzel einer Zahl näherungsweise berechnet werden Version 1: Genauigkeit 5 Stellen Argument/Ergebnis float Version 2: Genauigkeit 10 Stellen Argument/Ergebnis double Prototypen float wurzel(float); double wurzel(double); Es gibt kein Problem, da der Compiler anhand der Argumenttypen beide Funktionen trotz des identischen Namens unterscheiden kann. Das Unterscheidungsmerkmal wird als Signatur bezeichnet. Dr. Norbert Spangler /Informatik 21.11.2012 39 Signatur Unter der Signatur einer Funktion versteht man ihren Namen, sowie Anzahl und dem Typ der Parameter unter Berücksichtigung der Reihenfolge. Dies ist wichtig, um Funktionen mit gleichem Namen unterscheiden zu können. Der Compiler vergleicht die Signaturen und ruft dann entsprechend passende auf. Es wird ein Link-Fehler angezeigt, wenn es keine passende Funktion gibt. Die unterschiedliche Signatur sichert also die einheitliche Verwendung von Funktionsnamen für identische Aufgabentypen mit unterschiedlichen Datentypen. Dr. Norbert Spangler /Informatik 21.11.2012 40 Funktionen für unterschiedliche Typen Es gibt zahlreiche Aufgabenstellungen, wo nur der Typ der verwendeten Größen variiert, der Algorithmus jedoch identisch ist. - Mathematische Formeln in Abhängigkeit vom Datentyp (analog wurzel) - Vertauschen von Objekten vom identischen Typ Standardalgorithmus: typ h=x; x=y; y=h; - Suchen in Arrays - Sortieren von Arrays Es gibt eine elegante Möglichkeit in C++, anstelle der "vielen" Funktionen für jeden möglichen Parametertyp nur eine einzige zu schreiben: Funktionstemplates. (siehe letzten Abschnitt in diesem Kapitel). Dr. Norbert Spangler /Informatik 21.11.2012 41 6.6 Rekursion / Beispiele Rekursive Definition sind in der Mathematik sehr verbreitet { 1 n*(n-1)! falls n=0 falls n>0 { 1 x*x (n-1) falls n=0 falls n>0 Fakultät n! = Potenz xn = Fibonaccizahlen F0=0 F1 = 1 Fn = Fn-1 + Fn-2 Dr. Norbert Spangler /Informatik 21.11.2012 42 Programmierung Programmierung einer rekursiven Funktion in C++: In einer Funktion kann dieselbe Funktion wieder aufgerufen werden int fakultaet(int n) { if ( n==0 ) return 1; else return n*fakultaet(n-1); } double potenz(double x,int n) {//für n>=0 if ( n==0 ) return 1; else return x*potenz(x,n-1); } Dr. Norbert Spangler /Informatik int fibonacci (int n) { if ( n==0 ) return 0; else if ( n==1 ) return 1; else return fibonacci(n-1)+fibonacci(n-2); } 21.11.2012 43 Definition Rekursion (lat. recurrere) bedeutet Selbstbezüglichkeit d.h. etwas verweist auf sich selbst. Interpretation a: Eine Aufgabe lässt sich zerlegen in eine „einfach“ zu lösendes Teilaufgabe und eine weitere Aufgabe, welches vom selben Typ ist wie die Ausgangsaufgabe mit geringere Komplexität bzw. geringeren Umfang, die für „kleine“ Komplexität direkt lösbar ist. Beispiele: einfach fakultät(n) = n * Sortiere(n Zahlen) = größte suchen + Dr. Norbert Spangler /Informatik derselbe Typ/geringerer Umfang fakultaet(n-1) sortiere(n-1 Zahlen) 21.11.2012 44 Definition Rekursion (lat. recurrere) bedeutet Selbstbezüglichkeit d.h. etwas verweist auf sich selbst. Interpretation b: Divide and Conquer (Teile und Herrsche) Eine Aufgabe lässt sich zerlegen in zwei Teilaufgaben welche vom selben Typ sind wie die Ausgangsaufgabe aber mit geringere Komplexität bzw. geringeren Umfang. Für „ganz“ einfache Fälle ist die Aufgabe direkt lösbar. Beispiel Sortieren Zerlege das Array in 2 Teile Sortiere ( Teil 1) und Sortiere ( Teil 2) Mache aus den 2 sortierten Teilen ein sortiertes Array Einfacher Fall: nur 1 Zahl Beispiel Suchen Teste die Mitte – falls gefunden->fertig Falls gesuchtes Element <: Suche links weiter Falls gesuchtes Element größer-> Suche rechts weiter Dr. Norbert Spangler /Informatik 21.11.2012 45 Definition Was bedeutet geringere Komplexität / geringerer Umfang ? Zahlen kleinere Zahlen statt große Zahlen (vgl. Fakultät, Fibonacci) Daten wenige Daten statt viele Daten ( vgl. Suchen/Sortieren) Vektoren/Tabellen wenige Elemente/Zeilen statt vieler Elemente/Zeilen Ausdrücke einfache Ausdrücke statt geschachtelte ( i+1 <-> (((i+1)*f(2*x)+.... ) Dr. Norbert Spangler /Informatik 21.11.2012 46 Alternativen Anstelle der Rekursionen sind oft auch Schleifen möglich: Iterative Lösung int fakultaet(int n) { int ergebnis=1; for ( int i=1;i<=n;i++) ergebnis*=i; return ergebnis; } double potenz (double x,int n) { int ergebnis=1; for ( int i=1;i<=n;i++) ergebnis*=x; return ergebnis; } Dr. Norbert Spangler /Informatik int fibonacci(int n) { if ( n==0) return 0; if ( n==1) return 1; int a=0,b=1,c; for ( int i=2;i<=n;i++) { c=b+a; //Addition a=b; b=c; } Dies ist meistens möglich. Es gibt aber Gegenbeispiele 21.11.2012 47 Beispiel Ackermannfunktion A(0,n) = n+1 A(m+1,0) = A(m,1) A(m+1,n+1) = A(m,A(m+1,n) Hierfür gibt es keine Schleifenlösung. Dr. Norbert Spangler /Informatik 21.11.2012 48 Zusammenfassung Rekursive Programme sind in der Regel kürzer/einfacher zu formulieren Iterationen (Schleifen) sind in der Regel effizienter (Overhead durch Funktionsaufrufe fehlt) Häufig lassen sich Rekursionen durch Iterationen ersetzen. „Faustformel“ für „übliche“ Aufgaben: Jede Aufgabe lässt sich rekursiv lösen! Jede Rekursion lässt sich mittels Schleife realisieren! Dr. Norbert Spangler /Informatik 21.11.2012 49 Beispiel Suche Man testet ob es das letzte Element ist und sucht, falls nicht gefunden, im Rest mit derselben Technik. Ist kein Element mehr da zum Vergleich, dann wird nichts gefunden. int suche (int n, int a[],int element) { if ( n==0 ) return -1;//nicht gefunden else if ( a[n-1] ==element ) return n-1;//gefunden else return suche(n-1,a,element);// im Rest weitersuchen } Dr. Norbert Spangler /Informatik 21.11.2012 50 Türme von Hanoi Start Hilfe Ziel n Scheiben Alle Scheiben müssen von Start nach Ziel unter Verwendung von Hilfe umgelegt werden, wobei immer nur eine Scheibe bewegt werden und eine größere Scheibe nie auf eine kleinere gelegt werden darf. Tuerme(int anzahl,char start,char hilfe,char ziel) { if ( anzahl>0 ) { Tuerme(anzahl-1,start,ziel,hilfe); cout<<"von "<<start<<" nach "<<ziel<<endl; Tuerme(anzahl-1,hilfe,start,ziel); } } Dr. Norbert Spangler /Informatik 21.11.2012 51 Pythagoräischer Baum Baum(Seite) falls Seite zu kurz -> Ende errichte über der Seite ein Quadrat zeichne über der gegenüberliegenden Seite ein Dreieck mit vorgegebenen Winkeln Baum(neue Dreieckseite1) Baum(neue Dreieckseite2) Dr. Norbert Spangler /Informatik 21.11.2012 52 6.7 Testen von Funktionen Main f_eingabe f_verarbeitung f_algorithmus1 f_c f_a f_b f_ausgabe f_algorithmus2 f_c f_c f_d f_c Ein System mit mehreren Funktionen soll entwickelt werden. Problem: Vorgehensweise Orientiert man sich an der Hierarchie spricht man von inkrementellem Testen Dr. Norbert Spangler /Informatik 21.11.2012 53 Testen von Funktionen/ Top Down Main f_eingabe f_verarbeitung f_algorithmus1 f_a f_b f_ausgabe f_algorithmus1 f_c f_d Problem: Wenn main programmiert wird und dann getestet werden soll, stehen die benötigten Funktionen (hier 3 ) noch nicht zur Verfügung. Lösung: Es sind sogenannte Stubs mit identischen Schnittstellen (Parametern) zu erstellen, welche das Verhalten der benötigten Funktionen in einfachster Weise simulieren. Beispiel: f_eingabe: Es werden keine Daten gelesen sondern fest vorgegeben f_ausgabe: Eine einfache Bildschirmzeile: Aufruf Ausgabe wird erstellt f_verarbeitung: es wird ein plausibles Ergebnis fest vorgegeben. Dr. Norbert Spangler /Informatik 21.11.2012 54 Testen von Funktionen/ Bottom up Main f_eingabe f_verarbeitung f_algorithmus1 f_a f_b f_ausgabe f_algorithmus1 f_c f_d Problem: Wenn eine Funktion programmiert wird und dann getestet werden soll, steht die benötigte aufrufende Funktion und insbesondere main noch nicht zur Verfügung. Lösung: Es sind sogenannte Treiber zu erstellen, welche die programmierten Funktionen für Testzwecke aufrufen. Ein Treiber liest also (nur) Testdaten ein, welche genau diese Funktion benötigt und zeigt das Ergebnis an. Beispiele: ab sofort in jedem Praktikum Dr. Norbert Spangler /Informatik 21.11.2012 55 Top Down Gegenüberstellung Top Down/Bottom up (Auswahl) Vorteil : main wird als erstes erstellt; Vorführung und Einbeziehung der Anwender ist sehr früh möglich. Frühzeitige Erkennung von Entwurfsfehlern. Einfachere Tests, da die Funktionen nur in der späteren Einsatzumgebung getestet werden (müssen) - keine unnötigen theoretischen Tests. Nachteil: Es müssen Stubs erstellt werden. Die Funktionen werden nur in der späteren Einsatzumgebung getestet, sind daher nicht universell getestet Bottom up Vorteil : Funktionstest unabhängig von der Einsatzumgebung Die Funktionen werde sehr ausführlich getestet Nachteil: Es müssen Treiber erstellt werden. Vorführung und Einbeziehung der Anwender ist erst spät möglich. Spätes Erkennen von Entwurfsfehlern. Dr. Norbert Spangler /Informatik 21.11.2012 56 Testpläne für Funktionen Als " Eingabedaten" werden hier ausschliesslich Variationen der Parameter betrachtet. Damit gibt es keine unzulässigen Eingaben wie char statt int, denn die Parameter haben immer typkonforme Werte. Beispiel: double wurzel(a) Fall Beschreibung 1 Siehe Kapitel 1 4 5 Unzulässig Zeichen Dr. Norbert Spangler /Informatik a Ergebnis -1.0 ? b kann es nicht geben->keine Eingabe 21.11.2012 57 6.9 Sonstiges / Inline Der Mechanismus der Parameterübergabe mit dem "hohen" organisatorischen Mehraufwand Erstellen der Kopien der Parameter Abspeichern der Rücksprungadresse Rückgabe des Ergebnisses Rücksprung Entfällt. Stattdessen wird der Programmcode der Funktion direkt am jede Stelle des Aufrufs eingebaut. Vorteil: schneller Nachteil: längerer Code Anwendung: Schlüsselwort inline im Prototyp voranstellen inline typ name(Parameterliste); Dr. Norbert Spangler /Informatik 21.11.2012 58 Sonstiges/Funktionstemplate Ein Funktionstemplate legt die Anweisungen einer Funktion fest, wobei statt eines konkreten Typs ein Parameter verwendet wird. Damit kann "eine" Funktion für unterschiedliche Typen programmiert werden. Der Typ T ist der Parameter für die Vorlage. Der Aufruf kann mit oder ohne „Parameter“ erfolgen. Der Typ wird anhand der aktuellen Parameter bestimmt. Anmerkung: es können auch mehrere Typen als "Parameter" zum Einsatz kommen Dr. Norbert Spangler /Informatik 21.11.2012 59 Funktionstemplate/Beispiel Angabe der verwendeten Typen //Summation eines Arrays #include <iostream> using namespace std; template <class T> T summe(int n, T vec[]) { T help=0; for ( int i=0; i<n; i++ ) help+=vec[i]; return help; } void main() { int vec1[]={1,3,5,7,9}; double vec2[]={2.1,2.2,2.3}; Verwendung des Typs Aufruf int s1=summe(5,vec1);//Aufruf Variante 1- Typ ist int double s2=summe<double>(3,vec2);//Aufruf Variante 2 cout << " summe int "<<s1 <<endl; cout << " summe double "<<s2 <<endl; } Dr. Norbert Spangler /Informatik 21.11.2012 60