Grundlagen der Programmiersprache C für Studierende der Naturwissenschaften Teil 3: Schleifen- und Sprunganweisungen Patrick Schreier Abteilung für Angewandte Mathematik Vorlesung vom 03. Mai 2015 Gliederung Bedingte Anweisungen II Motivation Schleifenanweisungen for-Schleife while, do-while-Schleifen Sprunganweisungen Lösung gewöhnlicher Differentialgleichungen Gliederung Bedingte Anweisungen II Motivation Schleifenanweisungen for-Schleife while, do-while-Schleifen Sprunganweisungen Lösung gewöhnlicher Differentialgleichungen Programmbeispiel Quelltext (Wurzelberechnung mit Fehlerbehandlung) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <math.h> #include <stdio.h> int main(void) { /* declare floating point variable */ double x; /* read from user input */ printf ("x = "); scanf("%lf", &x); if( x < 0 ) { printf("value x is less than zero\n"); } else { printf ("x = %f\n", x); printf ("sqrt(x) = %f\n", sqrt(x)); } return 0; } if-else-Anweisung Syntax: if(expr) statement else statement Ob die Anweisungen des if- oder des else-Zweigs bearbeitet werden, hängt vom Kontrollausdruck ab: I Ist der Kontrollausdruck wahr, so wird der if-Zweig bearbeitet, I sonst werden die Anweisungen des else-Zweigs bearbeitet. Sowohl im if- als auch im else-Zweig können mehrere Anweisungen zu jeweils einem Block zusammengefasst werden. Dangling else Vorsicht bei verschachtelten Fallunterscheidungen: Der else-Zweig gehört immer zum vorangehenden if-Zweig! Beispiel: int a; ... if(a >= 0) if(a == 0) printf("a equals 0\n"); else printf("a is negative number\n"); Der Code liefert eine falsche Ausgabe für a > 0. Man spricht vom dangling else. Dangling else vermeiden Lösungsmöglichkeit 1 (leere Anweisung): int a; ... if(a >= 0) if(a == 0) printf("a equals 0\n"); else ; else printf("a is negative number\n"); Dangling else vermeiden Lösungsmöglichkeit 1 (leere Anweisung): int a; ... if(a >= 0) if(a == 0) printf("a equals 0\n"); else ; else printf("a is negative number\n"); Lösungsmöglichkeit 2 (Blöcke): int a; ... if(a >= 0) { if(a == 0) printf( "a equals 0\n" ); } else printf("a is negative number\n"); if-else if – Schreibweise Für komplexe, verschachtelte Fallunterscheidungen ist die folgende Schreibweise üblich: if(expr) statement else if(expr) statement else if(expr) statement ... else statementN Der else-Zweig und die Zusammenfassung von Anweisungen zu Blöcken ist wieder optional. Vorsicht, Falle I Gleitpunktoperationen sind immer mit einem kleinen Fehler behaftet. Gleitpunktzahlen sollten daher nie auf exakte Gleichheit überprüft werden: double x = (0.1 + 0.2) + 0.3; double y = 0.1 + (0.2 + 0.3); if(x == y) ... /* will be false */ Stattdessen kann man prüfen, ob zwei Gleitpunktzahlen hinreichend „nah“ beieinander liegen: if(fabs(x-y) < 1e-12) ... Vorsicht, Falle II Ein häufiger Fehler bei Vergleichen auf Gleichheit ist der folgende: if(x = 1) ... /* always true */ Der Kontrollausdruck besteht aus einer Zuweisung. Ihr Wert entspricht dem der linken Seite nach der Zusweisung (hier 1). Dies entspricht einer „wahren“ Aussage. Analog ist der folgende Kontrollausdruck immer „unwahr“: if(x = 0) ... /* always false */ switch-Anweisung Die switch-Anweisung prüft einen ganzzahligen Ausdruck auf mehrere konstante Alternativen hin ab. Syntax: switch(expr) { case const_expr1 : statement case const_expr2 : statement ... default : statementN } I Alle case-Konstanten müssen unterschiedliche Werte haben. I Stimmt der switch-Ausdruck expr mit einer der case-Marken überein, wird der Programmfluss hinter der Marke fortgesetzt. I Die default-Marke ist optional; stimmt der switch-Ausdruck mit keiner der angegebenen Konstanten überein, wird die default-Marke angesprungen. break in switch-Anweisungen Stimmt der switch-Ausdruck mit einer der case-Marken überein, werden alle Anweisungen danach ausgeführt – auch solche, die hinter späteren Marken stehen. Sollen nur die Anweisungen ausgeführt werden, die zu einer case-Marke gehören, muss dies explizit durch eine break-Anweisung erzwungen werden: int n = 1; switch(n) { case 1: printf("n = 1\n"); break; case 3: printf("n = 3\n"); break; case 2: printf("n = 2\n"); break; default: printf("n >= 4\n"); } Konditionaloperator Der einzige ternäre Operator ist der Konditionaloperator ?: expr1 ? expr2 : expr3 Der Wert des Typ und Wert des Ausdrucks stimmt entweder mit dem von expr2 oder expr3 überein und zwar abhängig von expr1: I Zuerst wird der Ausdruck expr1 ausgewertet. I Ist expr1 ungleich Null, so heißt der Ausdruck wahr. I Ist expr1 wahr, wird der Ausdruck expr2 ausgewertet, sonst expr3. Nur genau einer der Ausdrücke expr2, expr3 wird ausgewertet! Beispiel: c = (a < b) ? a : b /* c = min{a, b} */ Inkrement- und Dekrement-Operatoren Um eine Variable um den Wert 1 zu erhöhen oder um den Wert 1 zu erniedrigen gibt es in C die unären Operatoren ++ (Inkrementoperator) und -- (Dekrementoperator). Die Operatoren können auf alle arithmetischen Typen angewandt werden, und stehen entweder vor (Präfix-Notation) oder hinter (Postfix-Notation) der Variablen: ++x x++ −−x x−− x x x x wird um den Wert 1 erhöht, bevor x im Ausdruck weiterverwendet wird wird um den Wert 1 erhöht, nachdem x im Ausdruck verwendet wurde wird um den Wert 1 erniedrigt, bevor x im Ausdruck weiterverwendet wird wird um den Wert 1 erniedrigt, nachdem x im Ausdruck verwendet wurde Beispiel: int a = 0, b = 2; ++a; /* a = 1 */ b--; /* b = 1 */ Seiteneffekte Jeder Zuweisungsausdruck darf selbst wieder in einem Ausdruck auftauchen: int a = 1, b; b = (a += 1) + 1; /* a = 2, b = 3 */ Solche verschachtelten Ausdrücke verursachen sog. Seiten- oder Nebeneffekte, d. h. neben der Auswertung des Ausdrucks wird durch die Zuweisung auch der Wert einer Variablen geändert. Seiteneffekte machen den Code schwer lesbar und sind damit potentielle Fehlerquellen. Beispiel (Inkrement-Operatoren): int i = j = k = i, j, k; 1; i++; /* j = 1, side effect: i = 2 */ ++i; /* k = 3, side effect: i = 3 */ Komma-Operator Das Komma kennen wir bislang als Trennzeichen in Variablendefinitionen: int a1, a2, a3, a4; Es gibt auch den Komma-Operator: expr1, expr2 Seine Semantik ist etwas gewöhnungsbedürftig: I Zuerst wird der Ausdruck expr1 ausgewertet, dann expr2. I Der Typ und Wert des Gesamtausdrucks ist der Typ und der Wert des zweiten Operanden expr2. Beispiel: int a; double x; a = 1, x = 2.; /* value of expression is 2. */ Gliederung Bedingte Anweisungen II Motivation Schleifenanweisungen for-Schleife while, do-while-Schleifen Sprunganweisungen Lösung gewöhnlicher Differentialgleichungen Schleifenanweisungen Schleifenanweisungen . . . I sind Kontrollanweisungen, die die sonst sequentielle Verarbeitung der Programmanweisungen ändern. I führen eine Anweisung (oder mehrere Anweisungen in einem Block) wiederholt aus. I werden erst beendet, wenn ein vorher angegebenes Abbruchkriterium erreicht ist (oder die Schleife mit einer Sprunganweisung verlassen wird). In C gibt es mehrere Schleifenanweisungen zur Verfügung (for-, while- und do-while-Schleife). Auswertung monomialer Ausdrücke Wir wollen ein Programm schreiben, dass die n-Potenz x n einer reellen Zahl x berechnet. Der Definition eines Ausdrucks lässt sich nicht immer auch gleich ein geeigneter Algorithmus zu seiner Auswertung entnehmen: xn = n Y i=1 x, n Y i=1 x =x . . · x}, | · .{z n Mal 0 Y x = 1. i=1 Für unsere Zwecke besser geeignet ist die Darstellung in rekursiver Form: x n+1 = x · x n , x 0 = 1. Algorithmus in Pseudo-Code Wir halten den der rekursiven Formulierung oben entsprechenden Algorithmus in Form von Pseudo-Code fest. Pseudo-Code (n-te Potenz): y ← 1 for i = 0, 1,..., n-1 y ← x*y return y % initialization % number of iterations is fixed % loop body, y is updated in each step Im obigen Verfahren wird der Wert von y in jedem Iterationsschritt überschrieben. Damit kommt dem Startwert y ← 1 besondere Bedeutung zu. Gliederung Bedingte Anweisungen II Motivation Schleifenanweisungen for-Schleife while, do-while-Schleifen Sprunganweisungen Lösung gewöhnlicher Differentialgleichungen for-Schleife Die for-Schleife hat von allen Schleifenanweisungen die aufwändigste Syntax. Syntax: for(expr1; expr2; expr3) statement → Der erste Ausdruck expr1 heißt Initialisierungsausdruck. Er wird vor dem Kontrollausdruck und nur ein einziges Mal ausgewertet. for-Schleife Die for-Schleife hat von allen Schleifenanweisungen die aufwändigste Syntax. Syntax: for(expr1; expr2; expr3) statement → Der erste Ausdruck expr1 heißt Initialisierungsausdruck. Er wird vor dem Kontrollausdruck und nur ein einziges Mal ausgewertet. → An zweiter Stelle steht der Kontrollausdruck. Er wird vor dem ersten und dann nach jedem weiteren Schleifendurchlauf bewertet. Die Schleife wird beendet, sobald der Kontrollausdruck unwahr ist. for-Schleife Die for-Schleife hat von allen Schleifenanweisungen die aufwändigste Syntax. Syntax: for(expr1; expr2; expr3) statement → Der erste Ausdruck expr1 heißt Initialisierungsausdruck. Er wird vor dem Kontrollausdruck und nur ein einziges Mal ausgewertet. → An zweiter Stelle steht der Kontrollausdruck. Er wird vor dem ersten und dann nach jedem weiteren Schleifendurchlauf bewertet. Die Schleife wird beendet, sobald der Kontrollausdruck unwahr ist. → Der dritte Ausdruck wird nach jedem Schleifendurchlauf ausgewertet. Normalerweise werden hier Schleifenvariablen inkrementiert oder dekrementiert. Programmbeispiel (n-te Potenz) In einer for-Schleife lässt sich die n-te Potenz wie folgt berechnen: int i, n; double x, y; ... /* initialize x, n */ y = 1.; for(i = 0; i < n; ++i) y *= x; Zur Wahl der einzelnen Ausdrücke in der Schleife: for( i = 0 ; i < n; ++i) → An erster Stelle steht der Initialisierungsausdruck. In C beginnen Laufindizes in der Regel bei 0. Programmbeispiel (n-te Potenz) In einer for-Schleife lässt sich die n-te Potenz wie folgt berechnen: int i, n; double x, y; ... /* initialize x, n */ y = 1.; for(i = 0; i < n; ++i) y *= x; Zur Wahl der einzelnen Ausdrücke in der Schleife: for(i = 0; i < n; ++i ) → Nach jedem Schleifendurchlauf muss der Laufindex i inkrementiert werden. Programmbeispiel (n-te Potenz) In einer for-Schleife lässt sich die n-te Potenz wie folgt berechnen: int i, n; double x, y; ... /* initialize x, n */ y = 1.; for(i = 0; i < n; ++i) y *= x; Zur Wahl der einzelnen Ausdrücke in der Schleife: for(i = 0; i < n ; ++i) → Der Kontrollausdruck sorgt dann dafür, dass die Schleife soll nach n Iterationen beendet wird. Auswertung der Fakultät Ein weiteres einfaches Beispiel zur Verwendung der for-Schleife ist die Auswertung der n-ten Fakultat: n! = n Y i = 1 · 2 · . . . · n. i=1 Dieser Ausdruck lässt sich leicht in Pseudo-Code übertragen: Pseudo-Code (Fakultät): y ← 1 for i = 1, 2,..., n y ← i*y return y % initialization % range based iteration % index i is used in loop statement Programmbeispiel (Auswertung der Fakultät) Wie das folgende Programmbeispiel illustriert, ist es ohne weiteres möglich, den Laufindex i im Schleifenkörper zu verwenden: int factorial, i, n; ... /* initialize n */ for(factorial = 1, i = 1; i <= n; ++i) factorial *= i; Es kann mehr als eine Variable im ersten Ausruck der Schleife initialisiert werden (Komma-Operator). Analog können auch die übrigen Ausdrücke komplexer gestaltet werden: for(factorial = 1, i = 1; i <= n; factorial *= i++) ; Gliederung Bedingte Anweisungen II Motivation Schleifenanweisungen for-Schleife while, do-while-Schleifen Sprunganweisungen Lösung gewöhnlicher Differentialgleichungen Beispiel: Der Euklidische Algorithmus Der Euklidische Algorithmus ein klassisches Verfahren zur Berechnung des größten gemeinsamen Teilers zweier natürlicher Zahlen a, b. In Pseudo-Code lautet der Algorithmus: Pseudo-Code (Euklidischer Algorithmus): if a = 0 return b while b 6= 0 if a > b a ← a - b else b ← b - a return a Die Anzahl der Iterationsschritte (Anweisungen innerhalb von while und return) hängt von den Eingabegrößen a und b ab und ist nicht a-priori bekannt. while-Schleife Die while-Schleife hat von allen drei C-Schleifen die einfachste Syntax: while(expr) statement Der Ausdruck expr heißt Kontrollausdruck. Er muss von einem arithmetischen Typ (oder ein Zeiger) sein. → Der Kontrollausdruck wird mit allen Seiteneffekten ausgewertet. Ist er wahr (d. h. ungleich 0), wird die folgende Schleifenanweisung statement wiederholt ausgeführt. while-Schleife Die while-Schleife hat von allen drei C-Schleifen die einfachste Syntax: while(expr) statement Der Ausdruck expr heißt Kontrollausdruck. Er muss von einem arithmetischen Typ (oder ein Zeiger) sein. → Der Kontrollausdruck wird mit allen Seiteneffekten ausgewertet. Ist er wahr (d. h. ungleich 0), wird die folgende Schleifenanweisung statement wiederholt ausgeführt. → Nach jedem Schleifendurchlauf wird der Kontrollausdruck erneut ausgewertet. while-Schleife Die while-Schleife hat von allen drei C-Schleifen die einfachste Syntax: while(expr) statement Der Ausdruck expr heißt Kontrollausdruck. Er muss von einem arithmetischen Typ (oder ein Zeiger) sein. → Der Kontrollausdruck wird mit allen Seiteneffekten ausgewertet. Ist er wahr (d. h. ungleich 0), wird die folgende Schleifenanweisung statement wiederholt ausgeführt. → Nach jedem Schleifendurchlauf wird der Kontrollausdruck erneut ausgewertet. → Ist der Kontrollausdruck unwahr, wird die Schleife beendet. Abweisende Schleife Ob und wie häufig der Schleifenkörper (auch Schleifenrumpf genannt) durchlaufen wird, hängt vom Kontrollausdruck ab. In einer while-Schleife wird der Kontrollausdruck → vor dem Eintritt in die Schleife → und bei jedem erneuten Schleifendurchlauf ausgewertet. Die while-Schleife heißt auch abweisende Schleife: Ist der Kontrollausdruck bereits vor Eintritt in die Schleife unwahr, wird der Schleifenkörper nie durchlaufen (d. h. die nach while folgende Anweisung wird nicht ausgeführt). Programmbeispiel (Euklidischer Algorithmus) Quelltext #include <stdio.h> int main(void) { int a, b; printf("Enter a: "); scanf("%d", &a); printf("Enter b: "); scanf("%d", &b); if(a == 0) { printf("greatest common divisor(a,b) = %d\n", b); return 0; } while(b != 0) { if(a > b) a -= b; else b -= a; } printf("greatest common divisor(a,b) = %d\n", a); return 0; } Endlosschleifen Eine Schleife wird erst verlassen, wenn der Kontrollausdruck unwahr ist. Wird das Abbruchkriterium nie erreicht (d. h. bleibt der Kontrollausdruck immer wahr), kommt es zu einer Endlosschleife: while(1) ; /* results in infinite loop */ → Hängt ein Programm in einer Endlosschleife fest, muss es vom User oder vom System abgebrochen werden. → Der Compiler kann eine Endlosschleifen nicht entdecken: das Programm ist syntaktisch korrekt! Gleitpunktzahlen in Kontrollausdrücken Auch das folgende Beispiel liefert eine Endlosschleife: float x = 1./9.; while(x != 1.) x += 1./9.; Aufgrund von unvermeidbaren Approximationsfehlern liefert der Kontrollausdruck x != 1. stets den Wert 0 („Unwahr“). Eine Lösung dieses Problems könnte so aussehen: float x = 0.1; while(x <= 1.0) x += 0.1; do-while-Schleife Die do while-Schleife funktionert ähnlich wie eine while-Schleife mit dem Unterschied, dass der Schleifenkörper mindestens einmal durchlaufen wird. Syntax: do statement while(expr); → Die Anweisung statement (auch ein Block ist erlaubt) wird ausgeführt, danach wird der Kontrollausdruck expr ausgewertet. do-while-Schleife Die do while-Schleife funktionert ähnlich wie eine while-Schleife mit dem Unterschied, dass der Schleifenkörper mindestens einmal durchlaufen wird. Syntax: do statement while(expr); → Die Anweisung statement (auch ein Block ist erlaubt) wird ausgeführt, danach wird der Kontrollausdruck expr ausgewertet. → Dies wird solange wiederholt ausgeführt, wie der Kontrollausdruck wahr ist. do-while-Schleife Die do while-Schleife funktionert ähnlich wie eine while-Schleife mit dem Unterschied, dass der Schleifenkörper mindestens einmal durchlaufen wird. Syntax: do statement while(expr); → Die Anweisung statement (auch ein Block ist erlaubt) wird ausgeführt, danach wird der Kontrollausdruck expr ausgewertet. → Dies wird solange wiederholt ausgeführt, wie der Kontrollausdruck wahr ist. → Ist der Kontrollausdruck unwahr, wird die Schleife verlassen. Äquivalenz von for- und while-Schleife Alle Schleifenanweisungen sind äquivalent, d. h. man kann jede der drei Schleifen durch jede der jeweils anderen beiden – in Verbindung mit bedingten Anweisungen – ersetzen. Eine for-Schleife beispielsweise for(expr1; expr2; expr3) statement lässt sich stets äquivalent als while-Schleife formulieren: expr1 while(expr2) { statement expr3 } Gliederung Bedingte Anweisungen II Motivation Schleifenanweisungen for-Schleife while, do-while-Schleifen Sprunganweisungen Lösung gewöhnlicher Differentialgleichungen Sprunganweisungen Sprunganweisungen ändern den sequentiellen Programmablauf, indem ohne weitere Bedingungen an eine andere Stelle im Programm (im Quellcode) gesprungen wird. In C gibt es die folgenden Sprunganweisungen: I break I continue I goto I return Sprunganweisungen werden gebraucht . . . I um Schleifen oder eine switch-Anweisung (s. u.) zu verlassen oder I um ein Programm oder eine Funktion zu beenden. break-Anweisung Die break-Anweisung bewirkt, dass die innerste umgebende Schleife einer for-, while- oder do while-Anweisung oder eine switch-Anweisung sofort verlassen wird. Syntax: break; Beispiel: int i = 0; while(1) /* no infinite loop due to break */ { i++; if(i > 99) break; } continue-Anweisung Die continue-Anweisung darf nur innerhalb einer for-, whileoder do while-Schleife verwendet werden. Sie bewirkt, dass der aktuelle Schleifendurchlauf beendet wird und die Schleife mit der nächsten Iteration fortgesetzt wird. Syntax: continue; Beispiel: int i; for(i = 0; i < 10; ++i) { if(i%2) continue; printf("%d is an even number\n", i); } return-Anweisung Die return-Anweisung beendet eine Funktion. Handelt es sich bei dieser Funktion um main, so wird das Programm beendet. Syntax: return expr; Manche Funktionen (z. B. main) geben einen Wert zurück. Dies geschieht im Quellcode durch den Aufruf von return. Rückgabetyp und -wert stimmen dann mit expr überein. Auch return gehört zu den Sprunganweisungen: der Programmfluss wird an der Stelle fortgesetzt, an der die Funktion aufgerufen wurde. DIE Sprunganweisung – goto Mit der Sprunganweisung goto kann an eine beliebige andere, mit einer Marke ausgezeichnete Stelle (innerhalb derselben Funktion) gesprungen werden: 11: 12: printf( "First I'm here." ); goto label; 126: label: 127: printf( "Now I'm here."); Marken und Sprunganweisung Eine Marke (engl. label) zeichnet eine mögliche Einsprungstelle im Quellcode für die goto-Anweisung aus. Marken können an jeder beliebigen Stelle im Quellcode stehen. Eine Marke besteht aus einem Bezeichner und einem Doppelpunkt: label: statement Dem Standard nach muss auf eine Marke mindestens eine Anweisung (auch eine leere Anweisung) folgen. Mit der Sprunganweisung goto label; wird eine Marke innerhalb derselben Funktion angesprungen. Kritik an goto Die goto-Anweisungen wird häufig kritisch kommentiert. Das Urteil ist nicht immer einheitlich. Pro: + Tief verschachtelte Schleifen können z. B. im Fehlerfall gezielt verlassen werden. Contra: - Die Sprunganweisung kann immer mit anderen Sprachmitteln vermieden werden. - Extensiver Gebrauch macht Programme schwer lesbar und damit fehleranfällig. Manche Programmiersprachen unterbinden die Verwendung von goto (z. B. Java). Im Handbuch der Skriptsprache Lua hingegen werden die Vorteile unter bestimmten Bedingungen betont. Akademisches Beispiel: Schleifen mit goto Mit bedingten Sprunganweisungen (d. h. Kombination von if + goto) lassen sich die oben vorgestellten Schleifenanweisungen simulieren. Beispielsweise ist der folgende Code int i; i = 0; loop: if(i < 10) { printf("i = %d\n", i); ++i; goto loop; } äquivalent zu der for-Schleife: int i; for(i = 0; i < 10; ++i) printf("i = %d\n", i); Gliederung Bedingte Anweisungen II Motivation Schleifenanweisungen for-Schleife while, do-while-Schleifen Sprunganweisungen Lösung gewöhnlicher Differentialgleichungen Bakterienwachstum Obwohl wir erst wenige Sprachmittel zur Verfügung haben, können wir bereits einige interessante Fragestellungen praktisch untersuchen. Wir betrachten das Wachstum eines Bakterienkultur über einen Zeitraum [0, T ]. Zu Beginn der Beobachtung t = 0 werden p0 Bakterien geschätzt. Wir möchten ein Programm näherungsweise bestimmen lassen, wie groß die Bakterienpopulation p in der Probe zum Zeitpunkt t ∈ [0, T ] ist. Mathematische Modellierung I Angenommen, die Zahl der Bakterien p(t) zum Zeitpunkt t ist bekannt. Wir beobachten, dass sich kurze Zeit später, zum Zeitpunkt t + ∆t, die Population wie folgt verändert hat: p(t + ∆t) = p(t) + ∆tλp(t). Wir formulieren die obige Gleichung um zu p(t + ∆t) − p(t) = λp(t). ∆t Lassen wir nun ∆t → 0 gehen, so erhalten wir die gewöhnliche Differentialgleichung: p0 (t) = λp(t) für t ∈ (0, T ). Mathematische Modellierung II Die Größe der Population ist zum Zeitpunkt t = 0 bekannt. Die Lösung p erfüllt also das Anfangswertproblem p0 (t) = λp(t) für t ∈ (0, T ), p(0) = p0 . Das obige Problem hat eine eindeutige Lösung, die wir sogar angeben können: p(t) = p0 eλt Im allgemeinen sind in Anwendungen keine Lösungen in geschlossener analytischer Form gegeben. Dann ist man auf numerische Verfahren angewiesen. Numerisches Verfahren Sei ∆t eine gewählte Zeitschrittweite, dann setzen wir tk = k ∆t. Gesucht sind Näherungen pk ≈ p(tk ), wobei der Anfangswert p0 bekannt ist. Aus der Näherung p(t + ∆t) ≈ p(t) + ∆tλp(t) erhalten wir die folgende Vorschrift: pk +1 = pk + ∆tλpk . Dieses sog. explizite Verfahren lässt sich sehr einfach implementieren. Komplettes Programmbeispiel Quelltext (Unbeschränktes Wachstum) #include <stdio.h> int main(void) { double initial = .5; double rate = 1.; double time, endTime = 4.; double deltaT = 0.05; double population; for(time = 0., population = initial; time < endTime; time += deltaT) { printf("%f %f\n", time, population); population += deltaT*rate*population; } printf("%f %f\n", time, population); return 0; } Visualisierung von Daten I Das obige Programm liefert je nach gewählter Zeitschrittweite ∆t eine große Menge an Daten. Daten werden in der Regel zur späteren Verarbeitung (Postprocessing) in Dateien gespeichert. Für den Moment nutzen wir dazu die Unix-Shell. Ausgabe in Datei population.out umleiten: $ ./population > population.out Aufruf von gnuplot und Plotten der Daten: $ gnuplot gnuplot> plot "population.out" Visualisierung von Daten II 30 numerical solution exact solution 25 Population p(t) 20 15 10 5 0 0 0.5 1 1.5 2 2.5 3 3.5 Time t Abbildung : Ergebnis für λ = 1, p0 = 0.5, ∆t = 0.05 4 Logistisches Wachstum I Beim sog. logistischen Wachstum wird die Populationsgröße durch eine Kapazitätsgrenze C beschränkt. Das modifizierte mathematische Modell lautet: p0 (t) = λ(C − p(t))p(t) für t ∈ (0, T ), p(0) = p0 mit p0 ≤ C. Aus der Herleitung oben erhalten wir die folgende Vorschrift: pk +1 = pk + ∆tλ(C − pk )pk . Logistisches Wachstum II 1.4 numerical solution exact solution 1.2 Population p(t) 1 0.8 0.6 0.4 0.2 0 0 0.5 1 1.5 2 2.5 3 3.5 4 Time t Abbildung : Ergebnis für λ = 1, C = 1, p0 = 0.5, ∆t = 0.05 Autoren Autoren die an diesem Skript mitgewirkt haben: I 2011–2014 : Christoph Gersbacher I 2014 : Patrick Schreier This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) License. http://creativecommons.org/ licenses/by-sa/4.0/legalcode