Kapitel 3 Imperative Programmierung Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Imperative Programmierung • Funktionale Programmierung & Anweisung & Variable Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Prinzip • Programm = Folge von Anweisungen • Programm hat einen Zustand • Anweisungen ändern Zustand eines Programms Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Von Neumann Maschinen Register CPU Rechenwerk Cache Bus BefehlsIU zähler Daten, Befehle Hauptspeicher (RAM) Zustand = Hauptspeicher + Cache + Register Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Prinzip • Programm = Folge von Anweisungen • Programm hat einen Zustand • Anweisungen ändern Zustand eines Programms => Substitutionsmodell gilt nicht mehr! Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Auto • Anfangszustand: { leer, auf Parkplatz, Motor aus} • Einsteigen: { besetzt, auf Parkplatz, Motor aus} • Anlassen: { besetzt, auf Parkplatz, Motor läuft} • Losfahren: { besetzt, unterwegs, Motor läuft} • Ankommen: { besetzt, in Garage, Motor läuft} • Abstellen: { besetzt, in Garage, Motor aus} • Aussteigen: { leer, in Garage, Motor aus} Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Auto Variable und Zustand • Anfangszustand: { leer = wahr, Ort: 1, an = falsch} • Einsteigen: { leer = falsch, Ort: 1, an = falsch} • Anlassen: { leer = falsch, Ort: 1, an = wahr} • Losfahren: { leer = falsch, Ort: 2, an = wahr} • Ankommen: { leer = falsch, Ort: 0, an = wahr} • Abstellen: { leer = falsch, Ort: 0, an = falsch} • Aussteigen: { leer = wahr, Ort: 0, an = falsch} Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Befehlszähler als Variable PC: Befehlszähler • Anfangszustand: { leer = wahr, Ort: 1, an = falsch}, PC = 0, • Einsteigen: { leer = falsch, Ort: 1, an = falsch}, PC = 1, • Anlassen: { leer = falsch, Ort: 1, an = wahr}, PC = 2, • Losfahren: { leer = falsch, Ort: 2, an = wahr}, PC = 3, • Ankommen: { leer = falsch, Ort: 0, an = wahr}, PC = 4, • Abstellen: { leer = falsch, Ort: 0, an = falsch}, PC = 5, • Aussteigen: { leer = wahr, Ort: 0, an = falsch}, PC= stop • PC ist implizit • Der Programmierer sieht PC nicht. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Zustand • Zustand ist Abbildung Γ : V -> W (auch Umgebung genannt) • V = Menge aller Programmvariablen • W = Vereinigung der Wertebereiche aller Variablen • Häufig repräsentiert als Tabelle, sog. Bindungstabelle • Bsp: PC = 4 V PC leer Ort an W 4 falsch 0 wahr Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Umgebung (Zustand) • Ein Ausdruck wird immer relativ zu einer Umgebung ausgewertet, d.h. nur Ausdruck und Umgebung zusammen erlauben die Berechnung des Wertes eines Ausdruckes. • Die Zuweisung können wir nun als Modifikation der Bindungstabelle verstehen: nach der Ausfü̈hrung von y=5 gilt Γ(y) = 5. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Anweisung EBNF <Anweisung> ::= leere Anweisung | Abbruch | <Zuweisung> | <Folge> | <Bedingung> | <Schleife> | <Block> | <Funktionsaufruf> Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Leere Anweisung • Mache gar nichts • Bedeutung als Funktion auf Zustand: leer (Γ) = Γ • In Worten: Wenn man die leere Anweisung auf einen Zustand Γ anwendet, dann ist der Folgezustand wiederum Γ. (Vorsicht: Hier ist der PC nicht in Γ erfasst.) Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Abbruch • Breche Ausführung eines Programmes ab • Bedeutung des Abbbruchs: Abbruch (Γ) = • In Worten: Wenn man die Anweisung Abbruch auf einen Zustand Γ anwendet, dann ist im Folgezustand der Wert aller Variablen undefiniert. (Vorsicht: Der Wert von PC ist „stop“.) • Hinweis: Abbruch gibt es in C++ nicht. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Kommentare in C++ • Es ist guter Stil, Programme zu kommentieren „Literate Programming” (D. Knuth) /* Ich bin ein Kommentar */ // Ich bin auch ein Kommentar • N.B.: Kommentare sind keine Anweisungen! Insbesondere ist ein Kommentar kein NOP • Kommentare werden wie Leerzeichen oder Zeilenumbrüche in einem Vorverarbeitungsschritt entfernt. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Zuweisung • Weise einer Variablen einen neuen Wert zu. • <Zuweisung> ::= <Variable> := <Ausdruck> • Bedeutung der Zuweisung: (v:=e) (Γ) = Γ / [ v -> eval(Γ,e) ] In Worten: Werte den Ausdruck e mit Hilfe von Γ aus. Im Folgezustand hat v diesen Wert. Außer PC bleibt alles beim Alten. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Zuweisung in C++ • Verwende „=” anstelle von „:=” • Variablen sind getypt, Deklaration notwendig int v; /* Deklaration */ v = 5; /* legale Zuweisung */ v = „Die wilde Wutz“; /* Typfehler! */ • Konstante werden als „const“ deklariert const int v = 5; v = 5; /* Deklaration+Definition */ /* Fehler!!! */ Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Folge • Seien S1, S2 Anweisungen, dann ist auch S1 ; S2 eine Anweisung • <Folge> ::= <Anweisung> ; <Anweisung> • Bedeutung der Sequenz: (S1 ; S2) (Γ) = S2 ( S1 (Γ) ) • (Beachte wieder PC als Sonderfall.) • (Beachte: S ( ) = für beliebige S.) Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Verzweigung • <Verzweigung>::= if <Bedingung> then <Anweisung> else <statement> fi • Bedeutung der Verzweigung: (if C then S1 else S2 fi)(Γ)= S1(Γ), falls eval(Γ,C) = true S2(Γ), falls eval(Γ,C) = false , falls eval(Γ,C) = Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Verzweigung in C++ int fak (int x) { if (x == 0) return 1; else return x * fak (x-1); } Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Schleifen • <Schleife> ::= while <Bedingung> do <Anweisung> od • Solange Bedingung wahr, führe Anweisung aus (d.h. Bedingung wird vor jedem Durchlauf getestet) Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Beispiel: Summe int r:=0; int i:=0; while (i <= n) do r := r + i; i := i + 1; od Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Beispiel: Summe int r:=0; int i:=0; while (i <= n) do r := r + i; i := i + 1; od Bedeutung: n ! n(n + 1) r= i= 2 i=0 Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Carl Friedrich Gauß (1777 - 1855) 1 + 2 + 3 + … + 99 + 100 + 100 + 99 + 98 + … + 2 + 1 ______________________________ 101 +101 +101+… +101+ 101 100 * 101 = 2 * Summe G. Biermann: C.F. Gauß, 1777 - 1855 Der kleine Gauß Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Bedeutung der Schleife Bedeutung in jedem Schritt: i(i + 1) r= 2 • Schleifeninvariante (später in mehr Detail): – suche Schleifeninvariante als schwächste Bedingung, die vorher gilt und nach jedem Schleifendurchlauf Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt „While”-Schleife • Zwei Arten von While-Schleifen • while (B){A} – führe A solange aus, bis B nicht mehr erfüllt ist – Bedingung B wird überprüft bevor A ausgeführt wird => Die Schleife ist kopfgesteuert • do {A} while (B) – führe A solange aus, bis B nicht mehr erfüllt ist – Bedingung B wird überprüft nach der Ausführung von A => Die Schleife ist fußgesteuert Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt „For”-Schleife • for (A1; B; A2) {A3 } – führe A1 einmal vor Start der Schleife aus – führe A2 nach jedem Schleifendurchlauf aus – prüfe B; falls B wahr, führe A3 aus, sonst Ende – A1 ; while(B) {A3 ; A2 } – B wird überprüft bevor A3 ausgeführt wird. Die „For”-Schleife ist kopfgesteuert. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Fakultät mit while kopfges. long fak (int x) { long r = 1; int i = 1; while (i <= x) { r = r * i; i = i + 1; } return r; } Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Fakultät mit while fußges. long fak (int x) { long r = 1; int i = 1; do { r = r * i; i = i + 1; } while (i <= x); return r; } Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Fakultät mit For-Schleife long fak (int x) { long r = 1; int i = 1; for (i=1; i<=x; i++) { r = r * i; } return r; } Schleifeninvariante: i! => Lineare Rekursion läßt sich sehr gut durch eine Schleife ersetzen Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Fibonacci mit For-Schleife int fib ( int x) { int a = 1; int b = 1; int t; for (int i = 2; i<=x; i=i+1) { t = a + b; a = b; b = t; } return b; } Schleifeninvariante: fib(i) => Auch Baumrekursion läßt sich gut umsetzen, braucht aber mehr Technik Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Problem mit Notation Wir schreiben im Programm: t = a + b; a = b; b = t; mathematisch ergibt dies: t = 2a und a = t => a = 2a => a = 0, b = 0, t = 0 Bei der Umformung mathematischer Terme sind die Variablen stationär, d.h. nicht zeitabhängig. Beim Ablauf eines Programms spielt die Reihenfolge eine zentrale Rolle. Beides ist daher auseinanderzuhalten! Formal wird das beschrieben durch die Abfolge der Zustände des Programms. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Blöcke • Wir haben bereits Blöcke in C++ kennengelernt. Hier noch einmal die etwas formalere Behandlung • <Block> ::= begin [<Vereinbarung>] <Anweisung> end • <Vereinbarung> ::= var <Typ> <id>|{<Vereinbarung>}+ • In C/C++ verwendet man „{” und „}” anstatt „begin” und „end” • In C/C++ kann man Variable bei Deklaration initialisieren (d.h. kombinierte Deklaration und Zuweisung) • Semantik des Blocks ist Semantik der Anweisung allerdings mit einem neuen Zustand Γ Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Blöcke und Schleifen begin int a = 1; int b = 1; int i = 2; while i <= n do int t = a + b; a = b; b = t; i = i+1; od end Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Blöcke in C++ int dummy (int x) { cout << x << endl; { int x = 5; cout << x << endl; { double x = 7; cout << x << endl; } cout << x << endl; } cout << x << endl; } /* Wert des Parameters x */ /* Ausgabe 5 */ /* Ausgabe 7.0 */ /* Ausgabe 5 */ /* Wert des Parameters x */ Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Beobachtungen • Blöcke können verschachtelt sein! • Eine Variable ist „sichtbar“ (verwendbar), – wenn sie im aktuellen Block deklariert wurde oder – wenn sie in einem übergeordneten Block deklariert wurde. • Variablen können „überschrieben“ werden. • Achtung: Es gilt die jeweils nächstliegende Variablendefinition. • Innerhalb eines Blocks definierte Variable können nur innerhalb dieses Blockes und untergeordneter Blöcke verwendet werden. Variable sind blocklokal. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Baumrepräsentation v. Blöcken begin int v2, v5; begin int v1, v3; begin int v1, v2; end end begin int v6 end end Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Blöcke und Funktionen Wie sieht die Umgebung im Kontext mehrerer Funktionen aus? Programm: int g (int x) { int y = x*x; y = y*y; return h(y*(x+y)); } int h (int x) { return ((x<1000) ? g(x) : 88); } Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Lokalität bei Funktionen • Funktionen sind ebenfalls Blöcke • Jede Auswertung einer Funktion erzeugt eine eigene lokale Umgebung. Mit Beendigung der Funktion wird diese Umgebung wieder vernichtet! • Zu jedem Zeitpunkt der Berechnung gibt es eine aktuelle Umgebung. Diese enthält die Bindungen der Variablen der Funktion, die gerade ausgewertet wird. • In Funktion h gibt es keine Bindung fü̈r y, auch wenn h von g aufgerufen wurde. • Wird eine Funktion n mal rekursiv aufgerufen, so existieren n verschiedene Umgebungen für diese Funktion. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Lokalität bei Funktionen • Man beachte, dass eine Funktion kein Gedächtnis hat: Wird sie mehrmals mit gleichen Argumenten aufgerufen, so sind auch die Ergebnisse gleich. Diese fundamentale Eigenschaft funktionaler Programmierung ist also (bisher) noch erhalten. • Tatsächlich wäre obiges Konstrukt auch nach Einfü̈hrung einer main-Funktion nicht kompilierbar, weil die Funktion h beim Übersetzen von g noch nicht bekannt ist. Um dieses Problem zu umgehen, erlaubt C++ die vorherige Deklaration von Funktionen. In obigem Beispiel könnte dies geschehen durch Einfügen der Zeile int h (int x); vor die Funktion g. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Lokalität bei Funktionen int h (int x); int g (int x) { int y = x*x; y = y*y; return h(y*(x+y)); } int h (int x) { return ((x<1000) ? g(x) : 88); } Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Umgebungsmodell • Die Auswertung von Funktionen und Ausdrücken mit Hilfe von Umgebungen nennt man Umgebungsmodell (im Gegensatz zum Substitutionsmodell). Definition: (Umgebung) • Eine Umgebung enthält eine Bindungstabelle, d. h. eine Zuordnung von Namen zu Werten. Eigenschaften • Es kann beliebig viele Umgebungen geben. Umgebungen werden während des Programmlaufes implizit (automatisch) oder explizit (bewusst) erzeugt bzw. zerstört. • Die Menge der Umgebungen bildet eine Baumstruktur. Die Wurzel dieses Baumes heißt globale Umgebung. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Umgebungsmodell • Zu jedem Zeitpunkt des Programmablaufes gibt es eine aktuelle Umgebung. Die Auswertung von Ausdrücken erfolgt relativ zur aktuellen Umgebung. • Die Auswertung relativ zur aktuellen Umgebung versucht den Wert eines Namens in dieser Umgebung zu ermitteln, schlägt dies fehl wird rekursiv in der nächst höheren („umschließenden“) Umgebung gesucht. • Eine Umgebung ist also relativ kompliziert. Das Umgebungsmodell beschreibt, wann Umgebungen erzeugt, verändert oder zerstört werden. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Beispiel int x = 3; double a = 4.3; // 1 void main () { int y = 1; float a = 5.0; // 2 { int int a = ::a } y = 4; a = 8; 5*y; = 3.14; Umgebungsstruktur nach Zeile 5: globale Umgebung x int a double 3 3.14 main() y int a float 1 5.0 // 4 // 5 Block 1 in main() // 6 y int a int Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt 4 20 Eigenschaften einer Umbebung • In einer Umgebung kann ein Name nur höchstens einmal vorkommen. In verschiedenen Umgebungen kann ein Name mehrmals vorkommen. • Kommt auf dem Pfad von der aktuellen Umgebung zur Wurzel ein Name mehrmals vor, so verdeckt das erste Vorkommen die weiteren. • Eine Zuweisung wirkt immer auf den sichtbaren Namen. Mit vorangestelltem :: erreicht man die Namen der globalen Umgebung. • Ein Block besitzt eine eigene Umgebung. • Eine Schleife for (int i = 0; ... wird in einer eigenen Umgebung ausgeführt. Diese Variable i gibt es im Rest der Funktion nicht. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Globale Variablen • Funktionen haben kein Gedächtnis! Ruft man eine Funktion zweimal mit den selben Argumenten auf, so liefert sie auch dasselbe Ergebnis. Grund: • Funktionen hängen nur von ihren Parametern ab. • Die lokale Umgebung bleibt zwischen Funktionsaufrufen nicht erhalten. Wenn die Funktion verlassen wird, sind ihre lokalen Variablen undefiniert. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Beispiel: Konto • Ein Konto kann man einrichten (mit einem Anfangskapital versehen), man kann einzahlen (mit negativem Betrag auch abheben), und man kann den Kontostand abfragen. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Programm Konto #include <iostream> using namespace std; int konto; // die GLOBALE Variable void einrichten (int betrag) { konto = betrag; } int kontostand () { return konto; } int einzahlen (int betrag) { konto = konto+betrag; return konto; } int main () { einrichten(100); cout << einzahlen(-25) << endl; cout << einzahlen(-25) << endl; cout << einzahlen(-25) << endl; } Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Konto: Eigenschaften • Die Variable „konto” ist außerhalb jeder Funktion definiert. • Die Variable „konto” wird zu Beginn des Programmes erzeugt und nie mehr zerstört. • Alle Funktionen können auf die Variable „konto” zugreifen. Man nennt sie daher eine globale Variable. • Oben haben wir festgestellt dass Ausdrücke relativ zu einer Umgebung ausgeführt werden. In welcher Umgebung liegt die Variable „konto”? Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Bsp. Funktionsaufruf Umgebungen nach Marke 2 int konto; void einrichten (int betrag) { konto = betrag; // 2 } globale Umgebung konto int 100 main void main () { einrichten(100); // 1 } einrichten betrag int 100 Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Umgebungen und Funktionen • Jeder Funktionsaufruf startet eine neue Umgebung unterhalb der globalen Umgebung. Dies ist dann die aktuelle Umgebung. • Am Ende einer Funktion wird ihre Umgebung vernichtet und die aktuelle Umgebung wird die, in der der Aufruf stattfand. • Formale Parameter sind ganz normale Variable, die mit dem Wert des aktuellen Parameters initialisiert sind. • Sichtbarkeit von Namen ist in C++ am Programmtext abzulesen (statisch) und somit zur Übersetzungszeit bekannt. Sichtbar sind: Namen im aktuellen Block, nicht verdeckte Namen in umschließenden Blöcken und Namen in der globalen Umgebung. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Rekursiver Aufruf int fak (int n) Auswertung von fak(3) { globale Umgebung if (n==1) return 1; main else f int ? return n*fak(n-1); } fak n int 3 void main () { fak int f=fak(3); // 1 n int 2 } fak n int 1 Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt „Versteckte” Variable Versteckte Variable für Rückgabewert der Funktion Bsp.: fak(3) Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt