Objektorientierte Programmierung OOP
KE 2 Datentypen und Kontrollstrukturen; Begriffe, Beispiele und Definitionen
KE 2 Datentypen und Kontrollstrukturen; Begriffe, Beispiele und Definitionen
-
- 1 / 90
-
Flashcards
Beispiel: Typenverträglichkeit Operatoren
Bei der Zuweisung eines int-Werts an eine double-Variable
double x;
...
x = 3;
wird der int-Wert 3 vor der Zuweisung in den double-Wert 3.0 umgewandelt. Versuchen wir hingegen, eine int-Variable y mit einem double-Wert 3.0 zu belegen, so erhalten wir vom Übersetzer eine Fehlermeldung, da keine Typverträglichkeit vorliegt:
possible loss of precision
found : double
required: int
y = 3.0;
^
1 error
Definition: Auswertung einer Zuweisung
Eine Zuweisung der Form „v = e;“ wird in folgenden Schritten ausgewertet:
1. Der Typ der Variablen v wird ausgewertet.
2. Der Wert w(e) des Ausdrucks e wird ausgewertet.
3. Der berechnete Wert wird der Variablen zugewiesen, vorausgesetzt, dass der Typ des Ausdrucks e mit dem Typ der Variablen v verträglich ist.
Falls die Typen des Ausdrucks und der Variablen nicht identisch aber miteinander verträglich sind, wird der Typ des Ausdrucks in den Typ der Variablen umgewandelt. [JLS: § 5.2]
Beispiel: Division
Nach Ausführung der Initialisierung
double x = 1 / 4;
hat die Variable x nicht, wie vermutlich beabsichtigt, den Wert 0.25, sondern den Wert 0.0. Warum? Auf der rechten Seite wurde zunächst eine Division zweier intWerte ausgeführt. Das Ergebnis einer int-Division in Java ist ein int-Wert, wobei der Divisionsrest ignoriert wird. Die Auswertung der rechten Seite ergibt somit den int-Wert 0. Dieser wird anschließend, um der doubleVariablen x zugewiesen werden zu können, in den double-Wert 0.0 umgewandelt. Um zu erreichen, dass x den Wert 0.25 erhält, hätte mindestens einer der Werte auf der rechten Seite in Gleitpunkt-Notation geschrieben sein müssen. So bewirkt die Initialisierung
double x = 1.0 / 4;
dass bereits vor der Auswertung der Division der int-Wert 4 in den double-Wert 4.0 umgewandelt wird, die anschließende double-Division das Ergebnis 0.25 vom Typ double liefert und dieses (nun ohne weitere Umwandlung) der Variablen x zugewiesen wird.
Beispiel: Überschreiten des Wertebereichs
Nach der Ausführung von
int a = 2000000;
int b = 7000000;
long z = a * b;
ist in z der Wert -1593384960 gespeichert und nicht, wie es mathematisch korrekt wäre, 14000000000000, obwohl der Wertebereich von z diesen Wert zulassen würde. Das Ergebnis einer int-Multiplikation ist ein int-Wert. Die intMultiplikation resultiert in unserem Beispiel in einem sogenannten Überlauf (engl. Überlauf overflow), der bei einer Überschreitung des Wertebereichs auftritt. Eine Erweiterung des Wertebereichs hätte bereits vor der Multiplikation stattfinden müssen. Wäre mindestens eine der beiden Variablen a und b vom Typ long, so erhielten wir für z den erwarteten Wert 14000000000000:
int a = 2000000;
long b = 7000000;
long z = a * b;
Nun wird der Wert von a implizit als ein Wert vom Typ long aufgefasst und es wird eine long-Multiplikation ausgeführt.
Beispiel: Informationsverlust bei Typenumwandlung
Nach Ausführung der Initialisierung
float b = 1000000027;
mit impliziter Typerweiterungen von int nach float enthält die Variable b den Wert 1.0E9.
Beispiel: Typerweiterung von char-Werten zu Werten vom Typ int
Nach der Ausführung von
int x = ’A’ + 32;
hat x den Wert 97. ’A’ wurde zuvor in den int-Wert 65 umgewandelt.
Bemerkung: Unicode-Nummerierung von Zeichen
Die Erweiterung von Zeichen in numerische Werte folgt der Unicode-Nummerierung der Zeichen. Die Großbuchstaben ’A’ bis ’Z’ korrespondieren mit den Werten 65 bis 90, die Kleinbuchstaben ’a’ bis ’z’ mit den Werten 97 bis 122. Die Differenz zwischen zwei alphabetisch gleichen Klein- bzw. Großbuchstaben (z. B. ’a’ - ’A’) beträgt immer 32. Mit Hilfe der Typumwandlung lässt sich somit feststellen, ob zwei Zeichen alphabetisch gleich sind. Seien die char-Variable kleinBuchstabe mit irgendeinem Kleinbuchstaben und die char-Variable grossBuchstabe mit einem beliebigen Großbuchstaben belegt. Nach der Ausführung von boolean alphabetischGleich = (kleinBuchstabe - grossBuchstabe) == (’a’ - ’A’); enthält die Variable alphabetischGleich den Wert true, falls beispielsweise kleinBuchstabe und grossBuchstabe mit ’d’ und ’D’ belegt waren, oder den Wert false, falls beispielsweise kleinBuchstabe und grossBuchstabe mit ’d’ und ’M’ belegt waren.
Definition: Erzwungene Typumwandlung
Java unterstützt eine erzwungene Typumwandlung, um den Typ eines numerischen Werts während der Laufzeit in einen numerischen Typ ähnlichen Werts zu ändern. Die Syntax einer erzwungenen Typumwandlung ist:
(typeName) expression wobei typeName ein primitiver Typ und expression
ein Ausdruck ist. Die Typumwandlung wird von rechts nach links durchgeführt und liefert einen Wert vom Typ typeName, vorausgesetzt, dass die Konversion keine Kompatibilitätsregeln verletzt. [JLS: § 5.5, § 15.16]
double x = (double) 1 / 4; x=0.25
Beispiel: Überschreiten des Wertebereichs
„Überschreiten des Wertebereichs“ kann auch mit einer expliziten Erweiterung einer der zu multiplizierenden int-Variablen a und b gelöst werden.
int a = 2000000;
int b = 7000000;
long z = a * (long) b;
Beispiel: Typeinengung cast-Operator, erzwungene Typenumwandlungen
Nach Ausführung der drei Initialisierungen
double genau = 12345.6 / 7.8;
int ganzzahlig = (int) genau;
byte uebergelaufen = (byte) ganzzahlig;
hat die Variable genau den Wert 1582.769230769231, die Variable ganzzahlig den Wert 1582 und die Variable uebergelaufen den Wert 46. Wenn eine Gleitkommazahl in eine ganze Zahl umgewandelt wird, so wird nicht gerundet, sondern die Nachkommastellen werden abgeschnitten.
Beispiel: Geburtenrate, cast-Operator durch Klammerung die Auswertungsreihenfolge beeinflussen
Mit der Geburtenrate wird die durchschnittliche Anzahl der Geburten pro Jahr und 1000 Einwohner angegeben. Wir berechnen die innerhalb eines Jahres erwartete (ganzzahlige) Geburtenzahl in einer Gemeinde. Zur Berechnung wollen wir die exakte (Gleitkomma-)Geburtenrate verwenden: double geburtenrate = 8.25; int einwohner = 123456; int erwarteteGeburten = (int) (einwohner * geburtenrate / 1000);
Durch die Klammerung erreichen wir, dass erst das Ergebnis der Berechnung (die zuvor mit Gleitkomma-Genauigkeit durchgeführt wurde) auf einen ganzzahligen Wert eingeengt wird.
Beispiel: Umwandlung von int-Werten in char-Werte
Der Ausdruck
(char) 123
erzwingt eine Umwandlung des int-Werts 123 in das Zeichen ’{’, das mit dem Ganzzahlwert 123 in Unicode codiert ist.
Beispiel: Ableitung eines Großbuchstabens
Seien grossBuchstabe und kleinBuchstabe zwei Variablen vom Typ char, kleinBuchstabe sei mit einem Kleinbuchstaben initialisiert. Für die numerische Differenz zwischen gleichen Klein- und Großbuchstaben deklarieren wir eine Konstante:
final int ABSTAND = ’a’ - ’A’;
Bei der Zuweisung
grossBuchstabe = (char) (kleinbuchstabe - ABSTAND);
erfolgt zunächst eine implizite Erweiterung des Wertes von kleinBuchstabe nach int. Das int-Ergebnis der Subtraktion (beachten Sie die Klammern) wird explizit eingeengt auf den Typ char und kann anschließend der Variablen grossBuchstabe zugewiesen werden.
Bemerkung: Typeinengung bei char-Werten
Es verwundert zunächst, dass die Typumwandlung zwischen byte- oder short-Werten einerseits und char-Werten andererseits in beide Richtungen einengend, d. h. verlustbehaftet ist. Dies ist dadurch zu erklären, dass char-Werte nicht durch ein Vorzeichen gekennzeichnet sind. Der Wertebereich eines char-Wertes repräsentiert die Unicode-Nummerierung 0..65535, so dass sich die Wertebereiche von byte oder short (vgl. Tab. 9.1-1) und char nur überlappen, aber nicht überdecken.
Begriff: Kompatibilitätsregeln
Die mit Hilfe des cast-Operators möglichen Typumwandlungen fassen wir in folgenden Kompatibilitätsregeln (engl. compatibility rules) für einfache Typen zusammen:
• Jeder Typ kann in sich selbst umgewandelt werden.
• Jeder numerische Typ kann in jeden anderen numerischen Typen umgewandelt werden.
• Typ char kann in jeden numerischen Typen umgewandelt werden und jeder numerische Typ kann in Typ char umgewandelt werden.
• Typ boolean kann in keinen anderen Typ umgewandelt werden und kein andere Typ kann in Typ boolean umgewandelt werden.
Begriff: Anweisung
Anweisungen (engl. statement) sind die grundlegenden Programmierkonstrukte, aus denen ein Programm aufgebaut ist. Einfache Anweisungen haben wir bereits kennen gelernt: Ausdrücke, die mit einem Semikolon enden, sind Anweisungen. Jede Deklaration ist eine Anweisung. Oft sind Zuweisungen eigenständige Anweisungen.
Definition: Block
Eine Folge von Anweisungen, die in geschweiften Klammern „{“ und „}“ eingefasst sind, nennt man einen Block. Der Aufbau und die Abarbeitung eines Blocks werden schematisch im folgenden Aktivitätsdiagramm dargestellt. Die Anweisungen eines Blocks werden von oben nach unten sequenziell abgearbeitet. [JLS: § 14.2]
Beispiel: Block
Der folgende Programmauszug gibt ein Beispiel für einen Block:
{
int bestellteTulpen = 12;
int bestellteRosen = 16;
double preisTulpen = 0.80;
double preisRosen = 2.10;
double gesamtpreis = bestellteTulpen * preisTulpen + bestellteRosen * preisRosen;
}
Java-Konventionen: Blöck/Schreibweise
- In Blöcken sollten Einrückung verwendet werden. Die gebräuchliche Einheit für Einrückungen sind vier Leerzeichen. Wir nennen dies die 4-Leerzeichen-Regel (engl. 4-space rule).
- Jede Zeile innerhalb eines Blocks sollte höchstens eine Anweisung enthalten.
- Eine Zeile sollte nicht länger als 80 Zeichen sein. Falls eine Anweisung nicht in eine einzelne Zeile passt, trennen Sie sie entsprechend den folgenden allgemeinen Trennungsregeln in mehrere Zeilen auf:
- Trennen Sie nach einem Komma.
- Trennen Sie vor einem Operator.
- Richten Sie die neue Zeile so aus, dass der Beginn des Ausdrucks auf derselben Höhe beginnt wie der Ausdruck in der Zeile davor
Java bietet drei Arten von Kontrollstrukturen an, mit denen der Ablauf eines Programms formuliert werden kann: Bedingungs-, Wiederholungs- und strukturierte Sprunganweisungen.
Definition: if-Anweisung
Die if-Anweisung hat die folgende Form:
if (expression)
statement1
else
statement2
wobei
• expression ein boolescher Ausdruck sein muss und
• statement1 und statement2 Anweisungen sind.
Während der Ausführung des Programms wird eine if-Anweisung wie folgt ausgewertet:
• Zuerst wird der boolesche Ausdruck expression ausgewertet. Wenn das Ergebnis true liefert, wird statement1 ausgeführt, und der Kontrollfluss wird hinter der if-Anweisung fortgesetzt.
• Wenn die Auswertung von expression den Wert false liefert, wird statement1 ignoriert und stattdessen statement2 ausgeführt; danach wird die if-Anweisung verlassen. [JLS: § 14.9]
Die Semantik einer if-Anweisung wird in der Abbildung veranschaulicht: Der Alternativzweig kann bei einer if-Anweisung auch weggelassen werden, falls nur bei positiver Auswertung des Bedingungsausdrucks expression eine spezielle Anweisung ausgeführt werden soll: if (expression) statement.
Beispiel: if-Anweisung
if-Anweisung Mit der folgenden Anweisungsfolge können wir den Absolutwert einer ganzzahligen Variablen errechnen:
int absValue;
int x;
...
if (x >= 0)
absValue = x;
else
absValue = -x;
Java-Konventionen: if-Anweisung
Um derartige Konfusionen bei verschachtelten if-Anweisungen zu vermeiden, empfehlen wir dringend, die Zweige einer if-Anweisung als Block zu schreiben, auch wenn der Block nur eine Anweisung enthält:
if (expression) {
statement11
statement12
...
statement1m
} else {
statement21
statement22
...
statement2n }
Beispiel: Verschachtelte if-Anweisungen
if (x >= 0) {
if (x != 0) {
absValue = x;
}
} else {
absValue = -x;
}
Durch die Klammerung ist der Alternativzweig der ersten (äußeren) if-Anweisung zugeordnet. Die äußere if-Anweisung enthält eine weitere if-Anweisung ohne Alternativzweig
Definition: switch-Anweisung
Die switch-Anweisung hat die folgende Form:
switch (expression) {
case k1: statement1;
case k2: statement2;
...
case kn: statementn;
default: statementd;
}
wobei
• der Ausdruck expression vom Typ byte, short, int oder char sein muss;
• alle k1, ..., kn Konstanten des gleichen Datentyps wie expression oder eines damit verträglichen Typs sein müssen;
• statement1, ..., statementn und statementd Anweisungen sind.
Während der Programmausführung wird eine switch-Anweisung wie folgt ausgewertet:
• Zuerst wird expression ausgewertet. Falls das Ergebnis dieser Auswertung ein Wert ist, der mit einer der Konstanten ki für i = 1, ...., n übereinstimmt, dann werden die zugehörige Anweisung statement i und alle nachgeordneten Anweisungen statement i+1 bis statement n ausgeführt.
• Falls ein Standardfall (engl. default case) angegeben ist, wird die zugehörige Anweisung statement d abgearbeitet und danach die switch-Anweisung verlassen. statement d wird auch abgearbeitet, wenn das Ergebnis der Auswertung von expression mit keiner der Konstanten ki übereinstimmt. Ist kein Standardfall angegeben, so wird in diesem Fall keine der Anweisungen der Fallunterscheidung ausgeführt. [JLS: § 14.11]
Diese Semantik ist in der Abbildung grafisch dargestellt.
Bemerkung: switch-Anweisung
Die geschweiften Klammern sind ein zwingender Bestandteil der switchAnweisung
Beispiel: Mehrfach-Fallunterscheidung
Die Wirkung des folgenden Beispiels:
int weekdays;
...
switch (weekdays) {
case 1: System.out.println("Monday");
case 2: System.out.println("Tuesday");
case 3: System.out.println("Wednesday");
case 4: System.out.println("Thursday");
case 5: System.out.println("Friday");
case 6: System.out.println("Saturday");
case 7: System.out.println("Sunday");
default: System.out.println("there are no more weekdays"); }
ist, dass der Wert der Variablen weekdays bei der Ausführung der Fallunterscheidung darüber entscheidet, welche Tage der Woche angezeigt werden, wenn die switch-Anweisung ausgeführt wird. Falls weekdays den Wert 1 hat, werden alle Wochentage von Monday bis Sunday und der Text „there are no more weekdays“ angezeigt, jeweils in einer separaten Zeile.
Falls weekdays den Wert 6 bei Ausführung der switch-Anweisung hat, werden nur die Wochentage Saturday und Sunday sowie der Text „there are no more weekdays“ am Bildschirm ausgegeben. Falls weekdays bei Eintritt in die Fallunterscheidung einen Wert besitzt, der nicht im Intervall [1,7] liegt, erscheint dagegen nur die Nachricht „there are no more weekdays“ auf dem Bildschirm.
Definition: while-Anweisung
Eine while-Schleife hat die Form:
while (expression)
statement wobei
• expression ein Ausdruck vom Typ boolean sein muss und
• statement eine Anweisung ist.
Während der Programmausführung wird eine while-Anweisung wie folgt ausgewertet:
1. Der Ausdruck expression wird ausgewertet. Falls das Ergebnis dieser Auswertung den Wert true liefert, wird die Anweisung statement ausgeführt, dann wird Schritt 1 wiederholt.
2. Falls die Auswertung von expression das Ergebnis false liefert, wird die Ausführung der Schleife beendet. [JLS: § 14.12] Diese Semantik ist in Abbildung Abb. 14.1-1 dargestellt.
Beispiel: while-Schleife
Durch die Anweisungsfolge
int counter = 0;
while (counter < 10) {
System.out.println("Java rocks!");
counter++;
}
wird zehnmal die Zeichenkette "Java rocks!" geschrieben. Die Inkrementierung der Variablen counter in der letzten Zeile gewährleistet, dass nach dem zehnten Durchlauf der Bedingungsausdruck zu false evaluiert und die whileSchleife verlassen werden kann.
Das innere einer Schleife wird auch als Schleifenrumpf bezeichnet.
Java-Konventionen: einrücken
Es gilt die 4-Leerzeichen-Regel für Einrückungen. Falls eine expression getrennt werden muss, sollten Sie 8 Leerzeichen verwenden, da die 4-LeerzeichenEinrückung die Erkennung des Anweisungsrumpfs erschwert:
while ((condition1 && condition2)
|| (condition3 && condition4) {
statement;
}
Definition: do-Schleife
Die do-Schleife (engl. do loop) hat die folgende Syntax: do
statement
while (expression);
Bei dieser Schleifenart wird während der Laufzeit zuerst die Anweisung statement ausgeführt. Dann wird der Bedingungsausdruck expression überprüft. Falls das Ergebnis true liefert, wird die Schleife so lange wiederholt, bis expression das Ergebnis false liefert. [JLS: § 14.13] Dieser Sachverhalt ist in Abb. 14.1-3 veranschaulicht.
Im Gegensatz zur while-Schleife, bei der die Schleifenanweisung unter Umständen nie ausgeführt wird, wird bei einer do-Schleife die Schleifenanweisung immer mindestens einmal ausgeführt.
Schleifen, bei denen der Rumpf möglicherweise nie ausgeführt wird, nennen wir abweisende Schleifen. Wird der Rumpf immer mindestens einmal ausgeführt, so sprechen wir von nicht abweisenden Schleifen.
Definition: for-Schleife
Eine for-Schleife hat folgende Form:
for (forInit; booleanExpr; forUpdate)
statement
wobei
• der optionale Ausdruck forInit im Normalfall eine Liste aus einer oder mehreren Variablendeklarationen und Initialbelegungen enthält, die durch Kommata voneinander getrennt werden; die neu eingeführten Schleifenvariablen müssen alle vom gleichen Typ sein; sie sind außerhalb der for-Schleife nicht sichtbar; der Ausdruck forInit wird genau einmal zu Beginn der Ausführung der Schleife evaluiert;
• booleanExpr ein optionaler Vergleichsausdruck vom Typ boolean ist; er bestimmt den Endwert und wird vor jedem Wiederholungszyklus der Schleife überprüft; sobald die Auswertung false liefert, wird die Schleife beendet;
• forUpdate eine optionale, durch Kommata getrennte Folge von einer oder mehreren Anweisungen ist, die im Normalfall die Schleifenvariablen verändern; der Ausdruck wird in jedem Schleifendurchlauf nach der Ausführung von statement ausgewertet;
• statement eine Anweisung ist. [JLS: § 14.14.1]
Dieser Sachverhalt ist in der Abbildung veranschaulicht.
Beispiel: for-Schleife
Java-Konventionen: Variablenname in Schleifen
Bei der Vergabe von Namen für Schleifenvariablen darf von der Regel, Namen beschreibend und selbsterklärend zu wählen, abgewichen werden. Schleifenvariablen werden oft durch einzelne Buchstaben dargestellt, weil ihre Funktion meist aus dem Kontext heraus klar ist.
Bemerkung: for-Schleife
Ist keine booleanExpr vorhanden, so verhält sich die Schleife genauso, als wäre der Ausdruck zu true evaluiert worden.
break-Anweisung In allen Schleifen bewirkt die Ausführung einer break-Anweisung ohne Sprungmarke, dass die innerste Schleife, in der die break-Anweisung auftritt, sofort verlassen wird, ohne die restlichen Anweisungen im jeweiligen Block auszuführen. Die Programmausführung wird nach dem betroffenen Schleifenblock fortgesetzt. Die break-Anweisung ohne Sprungziel kann auch in MehrfachFallunterscheidungen verwendet werden, um die Ausführung der auf den ausgewählten Fall folgenden Anweisungen zu unterbinden. [JLS: § 14.15]