Kap. 3, Imperative Programmierung - G-CSC

Werbung
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
Herunterladen