Kapitel 3 Ausdr ¨ucke, Anweisungen, Kontrollstrukturen

Werbung
Kapitel 3
Ausdrücke, Anweisungen,
Kontrollstrukturen
Wir behandeln nun übersichtsartig die wichtigsten Sprachelemente in (höheren) Programmiersprachen
für die Formulierung von Algorithmen.
3.1
Variablen und Objekte
Ausdrücke beinhalten üblicherweise Variable, auf deren Wert bei der Auswertung zugegriffen wird.
In Java gibt es zwei unterschiedliche Arten von Variablen (bzw. Datentypen) nämlich Standardtypen
und Referenztypen.
Die Standardtypen sind die “eingebauten” Typen1
boolean
char
byte
short
int
long
float
double
1 Bit
16 Bit ganze Zahl ohne Vorzeichen
entspricht dem Zahlenwert eines Unicode-Zeichen
8 Bit ganze Zahl mit Vorzeichen
16 Bit ganze Zahl mit Vorzeichen
32 Bit ganze Zahl mit Vorzeichen
64 Bit ganze Zahl mit Vorzeichen
32 Bit Fließkommazahl
64 Bit Fließkommazahl
Bei diesen Typen wird unter dem Namen der Variablen stets der Wert angesprochen. In der Sequenz
int a = 1;
1 Auf
die rechnerinterne Darstellung der auftretenden Zahlen wird genauer in Kapitel 12 eingegangen.
Version vom 2. November 2004
31
32
KAPITEL 3. AUSDRÜCKE, ANWEISUNGEN, KONTROLLSTRUKTUREN
int b = 2;
a = a + b;
wird also in a + b auf die Werte 1 und 2 zugegriffen.
Alle anderen Typen in Java sind Referenztypen. Hierzu gehören
• Klassentypen
• Schnittstellentypen
• Arraytypen
Wir behandeln zunächst nur die Klassentypen. Jede Klasse stellt einen Datentyp dar. Die Werte von
Variablen eines Klassentyps heißen in Java Objekte. In
TextField input, output;
werden zwei Variablen der Klasse TextField deklariert.
Bei Referenztypen wird unter dem Namen der Variablen nicht der Wert (also das Objekt), sondern
die Adresse (Referenz) angesprochen, an der das Objekt im Speicher abgelegt ist. Man braucht daher
Methoden, um
• die Objekte im Speicher zu erzeugen,
• die Werte/Daten einzugeben,
• auf die Werte/Daten zuzugreifen.
Die Erzeugung der Objekte erfolgt (bis auf wenige Ausnahmen) mit der new-Anweisung und dem
Aufruf eines Konstruktors. In
output = new TextField (10);
ist TextField(10) ein Konstruktor (von mehreren) der Klasse TextField, der ein Textfeld mit 10
Zeichen konstruiert und im Speicher einrichtet. Ein anderer Konstruktor ist z. B.
output = new TextField("Coma I", 10);
der das Textfeld gleich mit dem String Coma I belegt und eine Länge von 10 Zeichen vereinbart.
Jede Klasse verfügt üblicherweise über mehrere Konstruktoren für Objekte, darunter einen DefaultKonstruktor ohne Argumente.
Die Eingabe von Daten erfolgt dann z. B. über die Methode setText() wie in
output.setText("Hallo!");
3.1. VARIABLEN UND OBJEKTE
33
Der Zugriff auf Daten entsprechend über die Methode getText()
output.getText();
Ausnahmen gibt es bei der Klasse String (als Zugeständnis an C-Programmierer). Hier ist neben der
Java-konformen Konstruktion
String str = new String("Hallo");
auch die direkte Zuweisung möglich
String str = "Hallo";
Ferner wird unter dem Namen einer Variablen vom Typ String in Ausdrücken stets der Wert (also
der String selbst) angesprochen.
Für alle Standardtypen gibt es in Java korrespondierende Klassen (sogenannte Wrapper-Classes), etwa
Integer für int, Double für double usw. Die Sequenz
int a = 64;
Integer A = new Integer(a);
erzeugt ein Integer Objekt mit Wert von a (also 64). Durch
int b = A.intValue();
wird der int-Variablen b der Wert des Objektes A zugewiesen, auf den mit der Methode intValue()
zugegriffen wird. Völlig falsch und nicht Typ-verträglich wäre hier die Zuweisung
int b = A;
da unter A die Adresse von A, aber nicht der Wert angesprochen wird.
Zur Illustration des Unterschiedes zwischen Adresse (Referenz) und Wert betrachte man folgende
Deklaration:
TextField input, output;
String str = new String("Hallo!");
Hierfür werden im Speicher Plätze für drei Referenzen angelegt. Die ersten beiden werden mit null
initialisiert, während die dritte eine Referenz auf den String Hallo enthält. Dabei steht die Konstante
null für die “leere” Adresse. Im allgemeinen nehmen Referenzen natürlich weniger Speicherplatz als
die eigentlichen Daten ein. Dies wird durch die unterschiedliche Größe der Speicherplätze angedeutet.
Die Referenz von str auf die eigentlichen Daten (den Wert) wird durch den Pfeil dargestellt.
34
KAPITEL 3. AUSDRÜCKE, ANWEISUNGEN, KONTROLLSTRUKTUREN
input
null
output
null
str
r
-
Hallo!
Durch
input = new TextField(str, 10);
output = new TextField("Wie geht’s?", 10);
str = "Gut!";
werden zwei neue TextField-Objekte mit Adressen input und output erzeugt, die die Werte
Hallo! bzw. Wie geht’s? haben. Ferner wird der Variablen str der neue Wert Gut! zugewiesen.
Es ergibt sich folgendes Speicherbild:
input
r
-
Hallo!
output
r
-
Wie geht’s?
str
r
-
Gut!
Durch die Zuweisung input = output wird input die Adresse von output (nicht der Wert!) zugeordnet. Danach werden unter input und output dasselbe TextField-Objekt mit Wert Wie geht’s?
angesprochen. Das Objekt mit Wert Hallo! kann jedoch nicht mehr (mit input) angesprochen werden. Es entsteht folgendes Speicherbild:
input
rP
PP
Hallo!
output
r
PP
q
P
-
str
r
-
Wie geht’s?
Gut!
Um in input denselben Wert wie in output abzuspeichern, muss man die Methoden getText() und
setText() verwenden:
input.setText(output.getText());
Den Unterschied zwischen Referenz und Wert zeigt auch folgendes Codefragment.
Integer intA = new Integer(10);
Integer intB = new Integer(10);
String compare = (intA == intB) + " " + intA.equals(intB);
Zunächst werden zwei verschiedene Integer Objekte intA und intB mit demselben Wert 10 erzeugt.
Der Boolesche Ausdruck (intA == intB) vergleicht die Referenzen von intA und intB, während
35
3.2. AUSDRÜCKE
intA.equals(intB) mittels der Methode equals() der Klasse Integer die Werte von intA und
intB vergleicht. Der String compare bekommt also den Wert true false zugewiesen. 2
3.2
Ausdrücke
Ein Ausdruck ist grob gesprochen eine Formel oder Rechenregel, die stets einen Wert (Resultat) spezifiziert. Der Ausdruck besteht aus Operatoren und Operanden.
Beispiele für Operanden sind Konstanten, Variablen, Funktionen; Beispiele für Operatoren sind die
arithmetischen Operatoren + - * / und die logischen Operatoren ! && || (nicht, und, oder). Operatoren sind immer in Zusammenhang mit zugehörigen Wertebereichen (im Programmiersprachen
Jargon: Datentypen oder Typen) zu sehen, z. B. ganze Zahlen oder Gleitkommazahlen. Sie werden
daher in Zusammenhang mit Datentypen in Kapitel 5 noch eingehender diskutiert.
Beispiele für Ausdrücke in Java sind:
a > b
a * b / c != c + d * e
(a + b) * 3 / 2
logischer Ausdruck
logischer Ausdruck
arithmetischer Ausdruck
Haben a,. . .,e die Werte 1, 2, . . . , 6, so liefern die Ausdrücke die Werte false, true, bzw. 4.
Der Vorrang von Operatoren ist im Zweifelsfall durch Klammern zu regeln. Bei gleichwertigen Operatoren erfolgt die Auswertung von links nach rechts.
3.2.1
Ausdrücke, genaue Erklärung
In Java unterscheidet man wie in C lvalues und rvalues. Beide sind Ausdrücke, jedoch können lvalues
in Zuweisungen nur links vom Zuweisungszeichen = stehen. Genauer: lvalues bezeichnen alles, was
Inhalt einer Speicheradresse ist (also insbesondere Variablen, während rvalues allgemein den Wert
eines Ausdrucks bezeichnet.
Ein Ausdruck hat ganz allgemein 3 Eigenschaften:
• Einen Wert oder Rückgabewert, der sich durch vollständige Auswertung des Ausdrucks ergibt.
• Einen Typ, nämlich den Typ seines Wertes. Bei Funktionsaufrufen ist dies der Typ des Rückgabewertes (Rückgabetyp).
• Einen Effekt. Darunter versteht man einen Effekt auf Speicherinhalte. Ergibt sich dieser nicht
aus der Zuweisung eines Wertes an einen lvalue, so nennt man dies einen Seiteneffekt.3 Seiteneffekte entstehen vor allem bei Funktionsaufrufen (Änderung von Parametern oder globalen
Variablen), aber auch bei vielen Operatoren.
2 Eine
einzige Ausnahme von der Unterscheidung zwischen Adresse und Wert gibt es bei Strings, vgl. Abschnitt 5.4.
wird auch die Zuweisung an einen lvalue als Seiteneffekt bezeichnet.
3 Manchmal
36
KAPITEL 3. AUSDRÜCKE, ANWEISUNGEN, KONTROLLSTRUKTUREN
3.2.2
Ausdrücke und einfache Anweisungen
Eine wichtige Regel in der C-Programmierung (und damit auch in Java) ist: Ein Ausdruck wird zu
einer (einfachen) Anweisung durch Anfügen eines Semikolons. In diesem Fall wird der Rückgabewert
unterdrückt, und Zuweisungen an lvalues und ggf. Seiteneffekte sind die einzigen Effekte.
Weitere einfache Anweisungen sind: Aufruf einer void-Funktion oder eine Definition, jeweils abgeschlossen durch ein Semikolon. Die leere Anweisung besteht nur aus einem Semikolon.
3.2.3
Beispiele
1. a = b
Dies ist ein Zuweisungsausdruck mit dem Zuweisungsoperator =. Dabei wird dem lvalue a (der
einen Speicherplatz bezeichnet) der Wert des rvalue b zugewiesen. Es wird also ein Speicherinhalt verändert; dies zählt aber nicht als Seiteneffekt, da es eine Zuweisung an einen lvalue ist.
Der Wert des Ausdrucks ist der Wert, der der linken Seite zugewiesen wird. Der Typ ist der
Typ der linken Seite.
Die einfache Anweisung
a = b;
weist also a den Wert von b zu. Der Wert des Ausdrucks a = b, also b, wird unterdrückt.
2. c += b = a
+= ist ein Zuweisungsoperator mit folgender Bedeutung: x += y ist äquivalent zu x = x +
y. In dem Ausdruck c += b = a ist c der lvalue und b = a der rvalue. Die Auswertung des
rvalue weist b (als Seiteneffekt!) den Wert von a zu. Der Wert des rvalues ist der Wert von a.
Zu diesem wird der Wert von c addiert. (wegen des += Operators) und die Summe dem lvalue
c zugewiesen. Ein Beispiel mit konkreten Werten:
a)
vorher
a
b
c
1
5
2
b)
nachher
a
b
c
1
1
3
← Seiteneffekt!
Solche Seiteneffekte sind zwar möglich, aber sollten tunlichst vermieden werden, da sie zu
unlesbaren Programmen führen. Die Zuweisungen
b = a;
c += b;
leisten dasselbe und sind klarer.
3. Zuweisungen in logischen Ausdrücken
Ein deutliches Beispiel für die Eigenschaften von Ausdrücken ist
3.3. DEFINITIONEN UND DEKLARATIONEN
37
if ((a = b) > c) c = a;
für int Variablen a,b,c. Die Zuweisung a = b liefert einen Wert (nämlich den von b). Ist dieser größer als der Wert von c, so wird c = a ausgeführt. Durch die Auswertung des Ausdrucks
a = b wird als Seiteneffekt a der Wert von b zugewiesen.
4. Der Inkrementoperator ++ (entsprechend --)
Er kann als Präfix (++a) oder Postfix (a++) auf einen arithmetischen Ausdruck a angewendet
werden. Ist a ein lvalue, so wird der Wert von a um 1 erhöht. Der Wert von a++ ist der Wert
von a; der Wert von ++a ist der Wert von a plus 1. Also:
a++;
++a;
bedeutet: erst benutzen, dann erhöhen
bedeutet: erst erhöhen, dann benutzen
Ein Beispiel mit Werten a == 2 und b == 4:
a = b++;
a = ++b;
a++;
++a;
3.3
weist a den Wert 4 zu und erhöht als Seiteneffekt den Wert von b auf 5.
weist a den Wert 5 zu und erhöht als Seiteneffekt den Wert von b auf 5.
weist a den Wert von 3 zu, dies ist kein Seiteneffekt, da a ein lvalue ist.
weist a den Wert von 3 zu, dies ist kein Seiteneffekt, da a ein lvalue ist.
Definitionen und Deklarationen
In Java unterscheidet man zwischen Deklaration und Definition. Deklarationen führen Identifier ein
und assoziieren mit ihnen Typen, so dass der Compiler aufgrund dieser Deklaration die Typverträglichkeit überprüfen kann (type checking). In Java erfolgt dabei automatisch eine Default-Initialisierung.
Definitionen sind Deklarationen, die zugleich (bei Variablen und Konstanten) den Identifiern Speicherplatz zuordnen oder (bei Funktionen) den Rumpf der Funktion aufführen. Jeder Identifier muss
deklariert sein, bevor er benutzt werden kann. Beispiele sind:
int a;
double x = 3.14;
int Square(int x)
{return x*x;}
int Square(int x);
deklariert die Integer Variable a.
definiert die Gleitkomma Variable x und
initialisiert sie zu 3.14.
definiert die Funktion f (x) = x2 . In den {...}
Klammern steht der Funktionsrumpf.
deklariert eine Funktion Square. Dem Compiler
sind dadurch Name, Rückgabetyp und
Argumenttyp bekannt.
Weitere Beispiele werden in Kapitel 7.1.3 diskutiert.
38
KAPITEL 3. AUSDRÜCKE, ANWEISUNGEN, KONTROLLSTRUKTUREN
3.4
Strukturierte Anweisungen
Neben den einfachen Anweisungen gibt es die strukturierten Anweisungen:
• zusammengesetzte Anweisung (Verbundanweisung)
• bedingte Anweisung
• wiederholende Anweisung
und daraus abgeleitete Anweisungen (z. B. die selektive Anweisung). Sie werden mit den nachfolgend beschriebenen Kontrollstrukturen gebildet, die in allen höheren Programmiersprachen existieren. Sie heißen Kontrollstrukturen, da mit ihnen kontrolliert“ wird, wie Programme intern in
”
Abhängigkeit von Bedingungen gesteuert werden, d. h., je nach Bedingung unterschiedliche Anweisungen ausführen, teilweise auch wiederholt. Man sagt auch, die Kontrollstrukturen legen den
Programmfluss“ fest. Neben den Kontrollstrukturen bilden Methoden und Klassen weitere wichtige
”
Bausteine zum Steuern und Strukturieren von Programmen. AUf sie wird ausführlich in Kapitel 7
eingegengen.
Für die Kontrollstrukturen werden wir neben einer umgangsprachlichen Beschreibung und einem Beispiel drei programmiersprachenunabhängige Darstellungen angeben, und zwar als:
• Pseudocode,
• Struktogramm,
• Flussdiagramm.
Danach wird auf die entsprechende Realisierung der Kontrollstrukturen in Java eingegangen.
Struktogramme (auch Nassi-Shneiderman-flowcharts genannt) und Flussdiagramme sind graphische
Beschreibungen von Algorithmen unter Verwendung der genannten Kontrollstrukturen.
Pseudocode leistet das gleiche in einer an Pascal orientierten, mit normalem Text durchsetzten Beschreibung. Die folgende Tabelle stellt die Pseudocode Äquivalente bereits eingeführter Java Konstrukte zusammen.
Pseudocode
:=
=
6
=
not
and
or
{. . .}
Java
=
==
!=
!
&&
||
/*...*/ bzw. //...
Bedeutung
Zuweisung
Test auf Gleichheit
Test auf Ungleichheit
logisches nicht
logisches und
logisches oder
Kommentare
39
3.4. STRUKTURIERTE ANWEISUNGEN
3.4.1
Zusammengesetzte Anweisung, Verkettung
Die zusammengesetzte Anweisung (compound statement) besteht in der Hintereinanderschaltung (Verkettung) von Anweisungen (einfachen oder strukturierten) M1 , . . . , Mt , die in der gegebenen Reihenfolge sequentiell abgearbeitet werden.
Beispiel 3.1 (Hypothekberechnung) Es ist der monatliche Betrag a zu berechnen, der zur Ablösung
einer Annuitätshypothek in Höhe von b EURO bei einer Laufzeit von n Jahren und jährlicher Verzinsung zum Zinssatz von z % anfällt.
Dazu stellen wir folgende Überlegung an. Der Betrag b wächst in n Jahren bei der angegebenen
Verzinsung auf b · (1 + z)n , falls keine Rückzahlungen erfolgen. Die monatlichen Zahlungen von a
müssen, wenn sie zum selben Zinssatz verzinst werden, nach n Jahren auf den selben Betrag von
b · (1 + z)n führen. Durch Gleichsetzung beider Beträge lässt sich a berechnen.
Sehen wir uns an, wie sich die monatlichen Zahlungen verzinsen. Im ersten Jahr wird 12a “angespart”.
Dieser Betrag wird allerdings erst ab dem zweiten Jahr verzinst.4 Er wächst also nach n Jahren auf
12a(1 + z)n−1 EURO. Entsprechend ergeben die Zahlungen im zweiten Jahr am Ende 12a(1 + z)n−2
EURO, usw. Insgesamt ist der Wert aller Zahlungen nach n Jahren auf
12a · (1 + z)n−1 + 12a · (1 + z)n−2 + . . . + 12a
angewachsen. Die Gleichsetzung der Beträge ergibt dann:
b · (1 + z)n = 12a · (1 + z)n−1 + 12a · (1 + z)n−2 + . . . + 12a
= 12a[(1 + z)n−1 + . . . + (1 + z)1 + (1 + z)0 ]
(1 + z)n − 1
= 12a
(1 + z) − 1
(1 + z)n − 1
= 12a
z
Hieraus folgt
a=
b (1 + z)n · z
·
12 (1 + z)n − 1
Dies resultiert in folgenden Algorithmus zur Berechnung von a.
4 Dies ist eine gerichtlich umstrittene Praxis vieler Banken. An sich müssten die Zahlungen bereits—wie bei
Sparguthaben—für Teile eines Jahres verzinst werden. Die entsprechende Rechnung bleibt als Übung überlassen.
40
KAPITEL 3. AUSDRÜCKE, ANWEISUNGEN, KONTROLLSTRUKTUREN
Algorithmus 3.1 (Hypothek Abtrag)
M1 Lese b ein (in EURO)
M2 Lese z ein (in %)
M3 Lese n ein (in Jahren)
M4 Berechne r := 1 + z
M5 Berechne R := rn
M6 Berechne a := (b/12)(R · z)/(R − 1)
M7 Gebe a aus
Die Verkettung erlaubt keine Verzweigung. Sie hat daher (ohne Verwendung weiterer Kontrollstrukturen) nur einen beschränkten Anwendungsbereich. Viele sogenannte “programmierbare” Taschenrechner der ersten Generation waren nur so programmierbar.
Der Pseudocode für die Verkettung lautet:
begin
M1 ;
M2 ;
..
.
Mt ;
end
Das Struktogramm für die Verkettung ist in Abbildung 3.1 angegeben, das Flussdiagramm in Abbildung 3.2.
M1
M2
..
.
Mt
Abbildung 3.1: Struktogramm der Verkettung.
−→
M1
−→
M2
−→ . . .−→
Mt
−→
Abbildung 3.2: Flussdiagramm der Verkettung.
In Java werden zusammengesetzte Anweisungen durch geschweifte Klammern dargestellt:
{
M1 ;
M2 ;
..
.
41
3.4. STRUKTURIERTE ANWEISUNGEN
Mt ;
}
Ein Beispiel für ein Java Programm mit nur einer zusammengesetzten Anweisung ist Programm 2.1
(Temperatur.java). Hier folgt ein weiteres für die Hypothekberechnung.
Programm 3.1 Hypothek.java
// Hypothek.java
//
// calculates monthly mortgage rate
import java.awt.*;
import java.applet.Applet;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class Hypothek extends Applet {
double
amount,
ratePerMonth,
interestRate,
interestFactor,
power;
period;
int
Label
TextField
//
//
//
//
//
//
total amount
monthly rate
interest rate in %
1 + interest rate
interestFactor^period
number of years
amountPrompt,
interestRatePrompt,
periodPrompt,
startPrompt;
// declare Labels
amountField,
interestRateField,
periodField;
// declare textfields for input
// setup the graphical user interface components
// and initialize labels and text fields
public void init() {
setLayout(new FlowLayout(FlowLayout.LEFT));
setFont(new Font("Times", Font.PLAIN, 14));
amountPrompt = new Label("Geben sie den Gesamtbetrag "
+ "des Darlehens in EURO an:");
amountField = new TextField(10);
amountField.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
calculateMortgage();
42
KAPITEL 3. AUSDRÜCKE, ANWEISUNGEN, KONTROLLSTRUKTUREN
}
});
interestRatePrompt = new Label("Geben Sie den Zinssatz "
+ "in % an:");
interestRateField = new TextField(10);
interestRateField.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
calculateMortgage();
}
});
periodPrompt = new Label("Geben Sie die Laufzeit "
+ "in Jahren an:");
periodField = new TextField(10);
periodField.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
calculateMortgage();
}
});
startPrompt = new Label("Drücken Sie Return "
+ "zum Start der Berechnung.");
add(amountPrompt);
add(amountField);
add(interestRatePrompt);
add(interestRateField);
add(periodPrompt);
add(periodField);
add(startPrompt);
}
// display the result as graphics
public void paint(Graphics g) {
g.drawString("Ihre monatliche Rate betraegt EURO "
+ ratePerMonth, 15, 150);
g.drawString("für EURO " + amount + " bei " + period
+ " Jahren und " + interestRate
+ " % Zinsen.", 15, 170);
}
// process user’s action on the input text fields
3.4. STRUKTURIERTE ANWEISUNGEN
43
public void calculateMortgage() {
// get input numbers
amount = Double.parseDouble(amountField.getText());
interestRate = Double.parseDouble(
interestRateField.getText());
period = Integer.parseInt(periodField.getText());
interestFactor = (100 + interestRate) / 100;
power = Math.pow(interestFactor, period);
ratePerMonth = (amount / 12) * power
* (interestFactor - 1)/(power - 1);
// round to Cent
ratePerMonth = (Math.round(ratePerMonth * 100)) / 100.0;
repaint(); // calls method paint for the whole applet
}
}
Hier wird die Funktion pow() aus der Klasse Math für die Potenzierung verwendet. pow(a,b) berechnet den Wert ab und gibt ihn als Rückgabewert zurück.
Um die Berechnung in jedem Eingabefeld auslösen zu können, wird an jedes Eingabefeld ein ActionListener hinzugefügt, der in allen Fällen dieselbe Methode calculateMortgage() aufruft.
Ferner nutzen wir die Graphikmöglichkeiten von Java zur Ausgabe. Die Anweisung repaint() veranlasst das Applet, sich selbst als Graphikobjekt zu sehen und die Methode paint() für sich selbst
auf zu rufen. In ihr wird die Methode drawString(str, x, y) verwendet, die den String str an
der Position (x,y) zeichnet. Dabei sind x,y int-Variablen, die die Anzahl der Pixel von der linken
oberen Ecke der Appletfläche angeben (nach rechts bzgl. x und nach unten bzgl. y. Das fertige Applet
ist in Abbildung 3.3 dargestellt.
Abbildung 3.3: Das Hypothek Applet.
44
3.4.2
KAPITEL 3. AUSDRÜCKE, ANWEISUNGEN, KONTROLLSTRUKTUREN
Selektion, Bedingte Anweisung
Die Selektion ermöglicht das Ansteuern einer Alternative in Abhängigkeit von Daten. Der Prototyp
der Selektion ist die if-Anweisung.
Die if-Anweisung
Der Pseudocode für die if-Anweisung lautet:
if B
then S1
else S2
Dabei ist B ein Boolescher Ausdruck und sind S1 , S2 beliebige Anweisungen (insb. wieder strukturierte
Anweisungen). Der Prozessor berechnet den Wahrheitswert (true bzw. false) von B und steuert in
Abhängigkeit davon S1 bzw. S2 an. Der else-Teil darf fehlen.
Beispiel 3.2 (Betrag) Der Betrag |a − b| der Differenz a − b zweier reeller Zahlen a, b ist zu berechnen.
Eine Lösung in Pseudocode lautet:
if a > b
then berechne a − b
else berechne b − a
Das Struktogramm der if-Anweisung ist in Abbildung 3.4 dargestellt, das Flussdiagramm in Abbildung 3.5.
HH
H
true HH
false
B
XXX
X
true
XXX
X
X
H
S1
S2
B
XXX
S1
Abbildung 3.4: Struktogramm der if-Anweisung.
In Java wird die if-Anweisung folgendermaßen gebildet:
if (B) B1 else B2
Auch hier darf der else-Teil fehlen. Die Codekonvention schlägt vor, if und else Teile immer mit
{...} zu klammern und dabei folgendes Einrückmuster zu verwenden:
45
3.4. STRUKTURIERTE ANWEISUNGEN
+
H
?
HH −
HH B H
?
?
S1
S2
?
?
H
HH −
HH B H
+
?
S1
?
Abbildung 3.5: Flussdiagramm der if-Anweisung.
if (B1 ) {
Anweisungen
} else if (B2 ) {
Anweisungen
} else (B1 ) {
Anweisungen
}
Die selektive Anweisung
Sollen mehr als die zwei Fälle true und false unterschieden werden, so ist dies mit der selektiven
Anweisung oder Selektion möglich.
Diese lässt sich als “geschachtelte” Variante der if-Anweisung auffassen. Sind etwa c1 , . . . cn die Werte, die der Ausdruck B annehmen kann, und soll beim Wert ci die Anweisung Si ausgeführt werden,
so lässt sich die entsprechende Selektion wie folgt in Pseudocode realisieren:
if B = c1
then S1
else if B = c2
then S2
else if . . .
..
.
else if B = cn
then Sn
Hierfür existiert in Pseudocode die folgende Kurzform:
case B of
c1 : S1 ;
c2 : S2 ;
46
KAPITEL 3. AUSDRÜCKE, ANWEISUNGEN, KONTROLLSTRUKTUREN
..
.
cn : Sn ;
end
Das zugehörige Struktogramm ist in Abbildung 3.6 dargestellt, das Flussdiagramm in Abbildung 3.7.
aa
c1 aaa
aa
a
c2 aaa
aa
B
aa
a
aa
a
aa
a
aa
a
S1
S2
cn aaa
a
...
Sn
Abbildung 3.6: Struktogramm der Selektion.
B
c1
?
S1
PP
PP
PP
P
PP
c2
?
S2
cn
?
...
Sn
r
?
Abbildung 3.7: Flussdiagramm der Selektion.
In Java existiert dafür die switch-Anweisung, siehe Übung.
Bei ineinandergeschachtelten if-Anweisungen kann es wegen fehlender else Teile zu Unklarheiten
bei der Zuordnung der else zu den if kommen. Falls dies nicht durch Klammerung mit {...}
geregelt wird, bezieht sich ein “hängendes” else immer auf das letzte if, auf das eine Zuordnung
möglich ist. Bei
if (Bedingung1 ) Anweisung1
47
3.4. STRUKTURIERTE ANWEISUNGEN
if (Bedingung2 ) Anweisung2
else Anweisung3
bezieht sich das else also auf das zweite if.
3.4.3
Wiederholung
Die Wiederholung ermöglicht die wiederholte Durchführung einer Anweisung (meist mit veränderten
Werten von Variablen). Die Häufigkeit der Wiederholung wird dabei durch eine Boolesche Bedingung
kontrolliert. Der Prototyp der Selektion ist die while-Anweisung.
Die while-Anweisung
Der Pseudocode für die while-Anweisung lautet:
while B do S
Dabei ist B ein Boolescher Ausdruck und ist S eine beliebige Anweisung (insb. wieder eine strukturierte Anweisung). Der Prozessor berechnet den Wahrheitswert (true bzw. false) von B vor jeder
Ausführung von S. Ist B = true so wird S ausgeführt, sonst nicht.
Falls der Wahrheitswert von B immer true bleibt, so wird S stets wieder ausgeführt. Man gerät dann
in eine sog. Endlosschleife. Sie stellt bei Anfängern einen häufig gemachten Programmierfehler dar.
Das Struktogramm der while-Anweisung ist in Abbildung 3.8 dargestellt, das Flussdiagramm in
Abbildung 3.9.
B
S
Abbildung 3.8: Struktogramm der while-Anweisung.
In Java wird die while-Anweisung wie folgt gebildet:
while ( B ) S
Die Codekonvention schlägt folgende einrückende Schreibweise vor:
while (B) {
Anweisungen S
}
48
KAPITEL 3. AUSDRÜCKE, ANWEISUNGEN, KONTROLLSTRUKTUREN
?
− HH
H
B
H
H
H
+
?
?
S
Abbildung 3.9: Flussdiagramm der while-Anweisung.
Beispiel 3.3 (Größter gemeinsamer Teiler) Für zwei positive natürliche Zahlen x, y ist der größte
gemeinsame Teiler ggT (x, y) zu berechnen.
Der größte gemeinsame Teiler von zwei positiven natürlichen Zahlen x und y ist die größte natürliche
Zahl, die x und y teilt. So ist ggT (12, 16) = 4 und ggT (12, 17) = 1.
Zur Berechnung des ggT nutzen wir folgende mathematische Aussage:
Lemma 3.1 Sind a, b ∈ N und ist a > b > 0, so ist
ggT (a, b) = ggT (a − b, b).
Beweis: Zeige:
1. Ist c ein Teiler von a und b, so auch von a − b und b.
2. Ist c ein Teiler von a − b und b, so auch von a und b.
Aus 1. und 2. folgt: a, b und a − b, b haben dieselben Teiler, und damit auch denselben größten gemeinsamen Teiler.
zu 1: Sei c ein Teiler von a und b. Dann existieren k1 , k2 ∈ N mit k1 · c = a und k2 · c = b. Hieraus folgt:
a − b = k1 · c − k2 · c = (k1 − k2 ) · c
Da a > b, ist k1 − k2 > 0. Also ist a − b ein Vielfaches von c und somit c ein Teiler von a − b. Da c
nach Annahme auch ein Teiler von b ist, ist c ein Teiler von a − b und b. Also gilt 1.
zu 2: Sei c ein Teiler von a − b und b. Dann existieren l1 , l2 ∈ N mit l1 · c = a − b und l2 · c = b. Hieraus
folgt:
a = (a − b) + b = l1 · c + l2 · c = (l1 + l2 ) · c
Also ist c ein Teiler von a. Da c nach Annahme auch ein Teiler von b ist, ist c ein Teiler von a und b.
Also gilt 2.
Dies führt zu folgendem Algorithmus zur Berechnung von ggT (x, y) (in Pseudocode).
3.4. STRUKTURIERTE ANWEISUNGEN
49
Algorithmus 3.2 (Größter gemeinsamer Teiler)
a := x; b := y;
while a 6= b do
if a > b then a := a − b
else b := b − a;
{ggT (a, b) = ggT (x, y)}
{ggT (x, y) = a = b}
ggT (x, y) := a;
Wir überlegen uns zunächst, dass dieser Algorithmus korrekt arbeitet.
Satz 3.1 Algorithmus 3.2 berechnet für zwei beliebige positive natürliche Zahlen x, y ihren größten
gemeinsamen Teiler.
Beweis: Lemma 3.1 garantiert, dass am Ende der if-Anweisung bei jedem Durchlauf der whileSchleife die Bedingung ggT (a, b) = ggT (x, y) gilt.
Da a oder b in jedem Durchlauf der while-Schleife um mindestens 1 kleiner wird und a und b positiv
bleiben, kann die while-Schleife höchstens max{x, y} mal durchlaufen werden. Also terminiert die
while-Schleife.
Beim Austritt aus der while-Schleife gilt a = b, also ggT (a, b) = a = b. Zusammen mit der oben gezeigten Gleichheit ggT (a, b) = ggT (x, y) folgt die Behauptung.
Ein Zahlenbeispiel: Für x = 28, y = 12 ergeben sich folgende Werte für a und b.
a: 28 → 16 → 4 → 4 → 4
b: 12 → 12 → 12→ 8 → 4
Nach dem 4. Schleifendurchlauf ist a = b = 4, und der Algorithmus terminiert mit ggT (28, 12) = 4.
Ein entsprechendes Java Programm lautet:
Programm 3.2 GGT.java
// GGT.java
//
// calculates gratest common divisor gcd(x,y)
// of natural numbers x and y
import java.awt.*;
import java.applet.Applet;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class GGT extends Applet {
50
KAPITEL 3. AUSDRÜCKE, ANWEISUNGEN, KONTROLLSTRUKTUREN
int
x, y, a, b, gcd;
Label
xPrompt,
yPrompt,
startPrompt;
message;
xField,
yField;
String
TextField
// natural numbers
// declare Labels
// declare String for Result
// declare textfields for input
// setup the graphical user interface components
// and initialize labels and text fields
public void init() {
setLayout(new FlowLayout(FlowLayout.LEFT));
setFont(new Font("Times", Font.PLAIN, 14));
setSize(380, 150);
xPrompt = new Label("Geben sie eine natürliche Zahl "
+ "x > 0 ein:");
xField = new TextField("24", 10);
xField.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
compute_gcd();
}
});
yPrompt = new Label("Geben Sie eine natürliche "
+ "Zahl y > 0 ein:");
yField = new TextField("36", 10);
yField.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
compute_gcd();
}
});
startPrompt = new Label("Drücken Sie Return zum "
+ "Start der Berechnung.");
add(xPrompt);
add(xField);
add(yPrompt);
add(yField);
add(startPrompt);
3.4. STRUKTURIERTE ANWEISUNGEN
51
message = "Der ggT von 24 und 36 ist 12.";
}
// display the result as graphics
public void paint(Graphics g) {
g.drawString(message, 15, 130);
}
// process user’s action on the input text fields
public void compute_gcd() {
// get input numbers
x = Integer.valueOf(xField.getText()).intValue();
y = Integer.valueOf(yField.getText()).intValue();
if (x <= 0 || y <= 0) {
message = "Bitte nur positive ganze Zahlen "
+ "eingeben!";
} else {
a = x;
b = y;
while (a != b) {
if (a > b) {
a = a - b;
} else {
b = b - a;
}
// gcd(x,y) == a
message = "Der ggT von " + x + " und " + y
+ " ist " + a + ".";
}
}
repaint();
}
}
Man beachte, dass unzulässige Eingaben von ganzen Zahlen (0 oder negative Zahlen) durch den Benutzer im Programm abgefangen werden. Dies ist notwendig, da das Programm sonst in eine Endlosschleife gerät. Das fertige Applet ist in Abbildung 3.10 dargestellt.
52
KAPITEL 3. AUSDRÜCKE, ANWEISUNGEN, KONTROLLSTRUKTUREN
Abbildung 3.10: Das GGT Applet.
Varianten der while-Anweisung
Bei der while-Anweisung wird S nie ausgeführt, wenn B bereits zu Anfang den Wert false hat. Oft
möchte man jedoch die Anweisung S mindestens einmal (unabhängig von B) ausführen. Dies ist mit
der while-Anweisung wie folgt möglich:
S;
while B do S
Da dies häufig vorkommt, gibt es hierfür die repeat-Anweisung als eigene Kontrollstruktur.
repeat S while B
oder, äquivalent dazu,
repeat S until not B
Der Prozessor führt erst S aus, dann wird B geprüft (post checking im Gegensatz zu pre checking bei
der while-Anweisung). Falls B noch erfüllt ist, so wird S erneut ausgeführt.
Das Struktogramm der repeat-Anweisung ist in Abbildung 3.11 dargestellt, das Flussdiagramm in
Abbildung 3.12.
In Java gibt es hierfür die do-while-Anweisung:
do
S
while ( B );
Laut Codekonvention soll sie wie folgt geschrieben werden:
do {
Anweisungen S
} while (B);
53
3.4. STRUKTURIERTE ANWEISUNGEN
S
not B
Abbildung 3.11: Struktogramm der repeat-Anweisung.
?
S
?
+ HH
H
H B
H
?
H
−
Abbildung 3.12: Flussdiagramm der repeat-Anweisung
Manchmal muss man eine feste Anzahl von Wiederholungen (Iterationen) von S machen (z. B. n mal).
Dies kann realisiert werden durch die Mitführung einer Kontrollvariablen z, die nicht in S vorkommt.
Diese wird üblicherweise als Zähler bezeichnet.
z := n;
while z > 0 do
z := z − 1;
S
Kurzform:
repeat S n times
Das Struktogramm hierfür ist in Abbildung 3.13 dargestellt.
In Java gibt es dafür das for-statement als eigene Kontrollstruktur, siehe Übung.
3.4.4
Mächtigkeit von Kontrollstrukturen
Mit den eingeführten Kontrollstrukturen Verkettung, Selektion und Wiederholung kann alles berechnet werden, was im intuitiven Sinne berechenbar ist. Der Nachweis hiervon wird in der Theorie der
Berechenbarkeit geführt.
54
KAPITEL 3. AUSDRÜCKE, ANWEISUNGEN, KONTROLLSTRUKTUREN
S
n times
Abbildung 3.13: Struktogramm der n-maligen Wiederholung.
Tatsächlich lässt sich dies bereits mit weniger Kontrollstrukturen erreichen, z. B. reichen die Verkettung und die while-Schleife bereits aus. (Übung: hierdurch ist die if-Anweisung simulierbar). Verkettung und Iteration (n-malige Wiederholung) reichen jedoch nicht aus. Man braucht die Möglichkeit,
beliebige Boolesche Ausdrücke B als Abbruchbedingung zu wählen.
Die gleiche Mächtigkeit erreicht man alternativ auch mit der Verkettung, der Selektion und der gotoAnweisung. Die goto-Anweisung ermöglicht es, zu beliebigen Stellen im Programm zu “springen”.
In älteren Programmiersprachen (Basic, Fortran) und in Assembler gehören goto-Anweisungen zum
Standard, in moderneren Sprachen sind sie verpönt, da die Programme durch sie meist unstrukturiert
werden und schwer zu verstehen und zu überprüfen sind.
In Java gibt es kein goto Statement, jedoch sind Sprünge partiell mit den abgeschwächten“ goto
”
Anweisungen break und continue erlaubt, bei denen auch Sprungstellen definiert werden können.
Wir erläutern hier nur zwei spezielle Situationen um aus geschachtelten if- oder while-Anweisungen
heraus zu springen. Hierbei sind keine selbst definierten Sprungstellen erforderlich.
Zum Verlassen von Schleifen “in der Mitte” gibt es die Anweisungen break und continue. Die
while Anweisung wird dann zu
while (true) {
...
if (!Fortsetzungsbedingung ) break;
...
}
bzw. zu
while ( Fortsetzungsbedingung ){
...
if (!Bedingung ) continue;
weitere Anweisungen
}
Im ersten Fall wird die while-Schleife ganz verlassen, im zweiten Fall werden die “weiteren Anweisungen” übersprungen und erfolgt der nächste Durchlauf der while-Schleife.
55
3.5. LITERATURHINWEISE
break kann auch in der if- bzw. switch-Anweisung auftreten, vgl. Übung.
Zum Abschluss geben wir in den Abbildungen 3.14 und 3.15 die verbesserte Version des Algorithmus
zur Berechnung der nächst größeren Primzahl zu einer gegebenen Zahl (vgl. Kapitel 2.3) in Form von
Struktogrammen an.
Lese Zahl n ein
Durchlaufe alle ungeraden Zahlen z > n der Reihe nach
k kein Teiler von z und k · k ≤ z
Teste alle ungeraden Zahlen k ab k = 3 bis k · k ≤ z
ob sie Teiler von z sind
kein Teiler von z gefunden
Gebe z aus
Abbildung 3.14: Struktogramm des Primzahl-Algorithmus (Grobversion).
Lese Zahl n ein
` ``
```
n gerade
```
true
```
false
```
``
z := n − 1
z := n
z := z + 2
k := 3
divisorFound := false
(divisorFound = false) and (k · k ≤ z)
XXX
X
true
z modulo k = 0
XXX
XXX
divisorFound := true
X
XXX
false
k := k + 2
divisorFound = false
Gebe z aus
Abbildung 3.15: Struktogramm des Primzahl-Algorithmus (Feinversion).
3.5
Literaturhinweise
Kontrollstrukturen werden in nahezu allen Büchern über Programmiersprachen behandelt. Geschichtliche Hinweise zur Entstehung der Kontrollstrukturen und Vergleiche zwischen verschiedenen Programmierstilen und
Sprachen finden sich in [Hor84].
56
KAPITEL 3. AUSDRÜCKE, ANWEISUNGEN, KONTROLLSTRUKTUREN
Bzgl. Java verweisen wir auf [CW98, DD01, Küh99].
Die Behandlung von Kontrollstrukturen, Ausdrücken, Typechecking usw. durch Compiler ist ausführlich in
[ASU86] dargestellt.
Eine Einführung in die Theorie der Formalen Sprachen ist in [Wii87] enthalten. Tiefer geht [HU79].
Herunterladen