252-0027
Einführung in die Programmierung I 10.0 Systema=sches Programmieren
Thomas R. Gross
Department Informa=k ETH Zürich
Copyright (c) Pearson 2013 and Thomas Gross 2016 All rights reserved.
Uebersicht
! 10.0 Beispiel
! 10.1 Darstellung von Variablen und Objekten
! 10.1 AsserIons
! 10.2 Pre/Post CondiIons (für Statement)
! 10.3 Pre/Post CondiIons (für Folgen von Statements)
2
RaIonale Zahlen
! Beispiel einer sinnvollen Klasse
! Idee: Klasse Rational für die Darstellung von raIonalen Zahlen
! Reelle Zahl die als Verhältnis zweier ganzer Zahlen dargestellt wird
! Warum: Die double Darstellung ist nur eine ApproximaIon
double x = (1.0 / 2.0) + (1.0/3.0) + (1.0/6.0);
System.out.println(x);
RaIonale Zahlen
! Beispiel einer sinnvollen Klasse
! Idee: Klasse Rational für die Darstellung von raIonalen Zahlen
! Reelle Zahl die als Verhältnis zweier ganzer Zahlen dargestellt wird
! Warum: Die double Darstellung ist nur eine ApproximaIon
double x = (1.0 / 2.0) + (1.0/3.0) + (1.0/6.0);
System.out.println(x);
Output: 0.9999999999999999
RaIonale Zahlen
! Exakte Darstellung
! 0.1 == 1 / 10
! Klasse RaIonal unterstützt die üblichen arithmeIschen OperaIonen
5 ab + c
d = ad +bc bd
ab – c
d = ad – bc bd
ab x c
d = ac
ab c d = . .
bd
ad bc Addition:"
Subtraction:"
Multiplication:"
Division:"
ImplementaIon von RaIonal
! Vorschau auf die nächsten Seiten
! Verschiedene Konstruktoren
! Overloading
! Default: 0, ein Argument: ganze Zahl; zwei Argumente: QuoIent
! Die Konstruktoren stellen sicher dass Zähler und Nenner auf die kleinst möglichen Werte “gekürzt” werden
! Da sich die Instanzen der Klasse nie ändern bleibt diese Eigenschae
bestehen
ImplementaIon von Rational
! Die OperaIonen (add, subtract, mulIply, und divide) für die vier GrundoperaIonen verwenden die dot-NotaIon –
! Einer der Operanden ist der implizite Parameter, der andere der in der DefiniIon aufgeführte Parameter
! Auf den impliziten Parameter wird durch this zugegriffen.
/**
* The Rational class is used to represent rational numbers, which * are defined to be the quotient of two integers.
*/
public class Rational {
/** Creates a new Rational initialized to zero. */
public Rational() { this(0);
} /**
* Creates a new Rational from the integer argument.
* @param n The initial value */
public Rational(int n) { this(n, 1);
}
Klasse RaIonal
skip code
/**
* The Rational class is used to represent rational numbers, which * are defined to be the quotient of two integers.
*/
public class Rational {
/** Creates a new Rational initialized to zero. */
public Rational() { this(0);
} /**
* Creates a new Rational from the integer argument.
* @param n The initial value */
public Rational(int n) { this(n, 1);
} /**
* Creates a new Rational with the value x / y.
* @param x The numerator of the rational number * @param y The denominator of the rational number */
public Rational(int x, int y) {
int g = gcd(Math.abs(x), Math.abs(y));
num = x / g;
den = Math.abs(y) / g;
if (y < 0) num = -num;
} /**
* Adds the rational number r to this one and returns the sum.
* @param r The rational number to be added * @return The sum of the current number and r */
public Rational add(Rational r) {
return new Rational(this.num * r.den + r.num * this.den, this.den * r.den);
}
Klasse Rational
/**
* Creates a new Rational with the value x / y.
* @param x The numerator of the rational number * @param y The denominator of the rational number */
public Rational(int x, int y) {
int g = gcd(Math.abs(x), Math.abs(y));
num = x / g;
den = Math.abs(y) / g;
if (y < 0) num = -num;
} /**
* Adds the rational number r to this one and returns the sum.
* @param r The rational number to be added * @return The sum of the current number and r */
public Rational add(Rational r) {
return new Rational(this.num * r.den + r.num * this.den, this.den * r.den);
} /**
* Subtracts the rational number r from this one.
* @param r The rational number to be subtracted
* @return The result of subtracting r from the current number */
public Rational subtract(Rational r) {
return new Rational(this.num * r.den - r.num * this.den, this.den * r.den);
} /**
* Multiplies this number by the rational number r.
* @param r The rational number used as a multiplier
* @return The result of multiplying the current number by r */
public Rational multiply(Rational r) {
return new Rational(this.num * r.num, this.den * r.den);
}
Klasse Rational
skip code
/**
* Subtracts the rational number r from this one.
* @param r The rational number to be subtracted
* @return The result of subtracting r from the current number */
public Rational subtract(Rational r) {
return new Rational(this.num * r.den - r.num * this.den, this.den * r.den);
} /**
* Multiplies this number by the rational number r.
* @param r The rational number used as a multiplier
* @return The result of multiplying the current number by r */
public Rational multiply(Rational r) {
return new Rational(this.num * r.num, this.den * r.den);
} /**
* Divides this number by the rational number r.
* @param r The rational number used as a divisor
* @return The result of dividing the current number by r */
public Rational divide(Rational r) {
return new Rational(this.num * r.den, this.den * r.num);
} /**
* Creates a string representation of this rational number.
* @return The string representation of this rational number */
public String toString() { if (den == 1) {
return "" + num;
} else {
return num + "/" + den;
}
Klasse Rational
skip code
/**
* Divides this number by the rational number r.
* @param r The rational number used as a divisor
* @return The result of dividing the current number by r */
public Rational divide(Rational r) {
return new Rational(this.num * r.den, this.den * r.num);
} /**
* Creates a string representation of this rational number.
* @return The string representation of this rational number */
public String toString() { if (den == 1) {
return "" + num;
} else {
return num + "/" + den;
} } /**
* Calculates the greatest common divisor using Euclid's algorithm.
* @param x First integer * @param y Second integer
* @return The greatest common divisor of x and y */
private int gcd(int x, int y) { int r = x % y;
while (r != 0) { x = y;
y = r;
r = x % y;
}
return y;
}
/* Private instance variables */
private int num; /* The numerator of this Rational */
private int den; /* The denominator of this Rational */
}
Klasse Rational
skip code
SimulaIon
! Wir wollen sehen wie ein einfaches Programm drei raIonale Zahlen addiert
! Danach werfen wir einen Blick hinter die Kulissen
! Das Ergebnis dieser Berechnung soll(te) exakt sein 1 sein
! Im Gegensatz zur Rechnung mit double
1 2
1 3
1
+ + 6
c sum b
a public void run(){
Rational a = new Rational(1, 2);
Rational b = new Rational(1, 3);
Rational c = new Rational(1, 6);
Rational sum = a.add(b).add(c);
println(a + " + " + b + " + " + c + " = " + sum);
}
TestRational
5 6
1 2
1 3
1 6
1 1
temporary result
1/2 + 1/3 + 1/6 = 1
skip simula9on
public Rational(int x, int y) {
int g = gcd(Math.abs(x), Math.abs(y));
num = x / g;
den = Math.abs(y) / g;
if (y < 0) num = -num;
}
y g
x this
num
den 1 2 1
1 2
public Rational(int x, int y) {
int g = gcd(Math.abs(x), Math.abs(y));
num = x / g;
den = Math.abs(y) / g;
if (y < 0) num = -num;
}
y g
x this
num
den 1 3 1
1 3
public Rational(int x, int y) {
int g = gcd(Math.abs(x), Math.abs(y));
num = x / g;
den = Math.abs(y) / g;
if (y < 0) num = -num;
}
y g
x this
num
den 1 6 1
1 6
public Rational add(Rational r) {
return new Rational( this.num * r.den + r.num * this.den , this.den * r.den );
}
this
num den
1 2
r
num den
1 3 6
5 public Rational(int x, int y) {
int g = gcd(Math.abs(x), Math.abs(y));
num = x / g;
den = Math.abs(y) / g;
if (y < 0) num = -num;
}
y g
x this
num
den 5 6 1
5 6
public Rational add(Rational r) {
return new Rational( this.num * r.den + r.num * this.den , this.den * r.den );
}
this
num den
5 6
r
num den
1 6 36
36 public Rational(int x, int y) {
int g = gcd(Math.abs(x), Math.abs(y));
num = x / g;
den = Math.abs(y) / g;
if (y < 0) num = -num;
}
y g
x this
num
den 36 36 36
1 1
c sum
b a
public void run(){
Rational a = new Rational(1, 2);
Rational b = new Rational(1, 3);
Rational c = new Rational(1, 6);
Rational sum = a.add(b).add(c);
System.out.println(a + " + " + b + " + " + c + " = " + sum);
}
1 2
1 3
1 6
1 1
Based on Eric Roberts, The Art and Science of Java
Darstellungsfragen
! Warum ergibt eigentlich
double x = (1.0 / 2.0) + (1.0/3.0) + (1.0/6.0);
System.out.println(x);
Output: 0.9999999999999999
! Werte müssen im Speicher des Computers dargestellt werden
! Speicher organisiert als eine Gruppe von Bits
! Bit: binary digit
20
Darstellungsfragen
! Ein Bit hat zwei Zustände: normalerweise mit 0 und 1 bezeichnet
! Gruppen von 8 Bits bilden ein Byte
! Nur zur Vereinfachung der ImplementaIon des Rechners
! (Ganze und reelle) Zahlen werden in einer Folge von Bytes gespeichert
! Eine Folge von Bytes heisst ein Word. Computer können oe ein Word schnell verarbeiten (schneller als 4 beliebige aufeinanderfolgende Bytes)
210 0 1 0 1 0 1 0
Darstellungsfragen
! Eine ganze Zahl wird oe in einem Word aus vier Bytes gespeichert
! Manche Computer bezeichnenals ein Word als Folge von 8 Bytes
! Und speichern dann eine ganze Zahl in 64 Bits
22
Binärdarstellung
! Wenn wir die Bits eines Bytes als Binärzahl interpreIeren so können wir in einem Byte eine ganze Zahl darstellen
! Binärzahlen sind wie Dezimalzahlen aber verwenden eine andere Basis
! Dezimalzahlen Basis == 10
! Das Gewicht einer Ziffer ist das 10-fache des Gewichts der Ziffer zur Rechten
! Binärzahlen Basis == 2
! Das Gewicht einer Ziffer ist das Doppelte des Gewichts der Ziffer
zur Rechten
Binärdarstellung
0 0 1 0 1 0 1 0
0 × 1 0
1 × 2 2
0 × 4 0
1 × 8 8
0 × 16 0
1 × 32 32
0 × 64 0
0 × 128 0
42
Darstellungen einer Zahl
! Die Binärdarstellung 00101010 ist äquivalent zur Dezimaldarstellung 42
! Wenn die Basis wichIg ist, dann wird sie als Iefgestellt
! 00101010
2= 42
10! Aber es ist immer dieselbe Zahl
! Zahlen haben keine Basis, nur Darstellungen
25
Oktal- und Hexadezimalschreibweise
! 8 Bits sind mühsam zu schreiben
! Leicht etwas zu übersehen (00101010 oder 00101011)
! InformaIker verwenden daher oe die Oktalschreibweise (oder die Hexadezimalschreibweise)
! Oktal: Ziffern 0 … 7
! Hexadezimal: Ziffern 0 …. 9 A B C D E F
26
Oktal- und Hexadezimalschreibweise
! Die Zahl 00101010 in Oktal- und Hexadezimalschreibweise
! Der Vorteil dieser Darstellungen ist dass sie leicht in die Binärdarstellung umgewandelt werden können
! Jede Ziffer kann einzeln umgewandelt werden
28 2 x 1 25 x 8 40
5 2
42
octal
10 x 1 10 02 x16 32
2 A
42
hexadecimal
Uebung: Zahlendarstellungen
! Was ist die Dezimaldarstellung für diese Zahlen
100012 1778
AD
161 x 1 1 0 x 2 0 0 x 4 0 0 x 8 0 1 x 16 1
1 0 0 0 1
17 17
Uebung: Zahlendarstellungen
! Was ist die Dezimaldarstellung für diese Zahlen
100012 1778
AD
167 x 1 7 7 x 8 56
7 7
127
1
1 x 64 64 127
Uebung: Zahlendarstellungen
! Was ist die Dezimaldarstellung für diese Zahlen
100012 1778
AD
1613 x 1 13 10 x16 160
A D
173 173
! Dies sind die ersten 16 Bits jeder .class file
! Was ist diese Zahl in HexadezimalnotaIon?
1 1 0 0 1 0 1 0 1 1 1 1 1 1 1 0
1 1 0 0 1 0 1 0 1 1 1 1 1 1 1 0 F
CAFE
161 1 0 0 1 0 1 0 1 1 1 1 1 1 1 0
Start jeder .class File
od –t x1 Foo.class
[Linux]
Speicher und Addressen
• Jedes Byte im Speicher hat eine Adresse – eine Zahl zwischen 0 und einer Obergrenze
• Im Beispiel sind Adressen in Hexadezimal- notaIon mit 4 Stellen angezeigt
• Im Java System sind die Adressen unsichtbar und das Bespiel zeigt willkürliche Werte
• IllustraIonen mit Bytes sind unübersichtlich daher fassen wir 4 Bytes zu einem Wort zusammen und zeigen immer Wortadressen (die dann in 4-er Schrixen ansteigen)
0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B
FFF4 FFF5 FFF6 FFF7 FFF8 FFF9 FFFA FFFB FFFC FFFD FFFE FFFF 0000 0004 0008 000C 0010 0014 0018 001C 0020 0024 0028 002C
FFD0 FFD4 FFD8 FFDC FFE0 FFE4 FFE8 FFEC FFF0 FFF4 FFF8 FFFC . . . . . .
Speicherplatz für Variable
! Für eine deklarierte Variable muss das Laufzeitsystem Speicherplatz finden
! Der Speicher des Computers ist in verschiedene Bereiche unterteilt
! Genauer: der Teil des Speichers den unser Java Programm nutzen darf
! Ein Bereich ist reserviert für die Variablen die immer
exisiteren (während der gesamten Laufzeit des Programms)
! Dazu gehörten Konstanten, InformaIonen über Klassen, etc.
! Dieser Bereich heisst “staIc data”
Speicherplatz für Variable
! Objekte die durch den new Operator erschaffen werden, werden im “heap” (Halde) abgelegt.
! Für jede aufgerufene Methode stellt das Laufzeitsystem einen neuen Bereich zur Verfügung
! Dieser heisst “Stack Frame”
! Alle Stack Frames werden in einem Stack organisiert
! Variable, die in einer Methode deklariert wurden, finden Platz im Stack Frame
! Parameter überigens auch …
36
staIc data
0000
stack
heap
! Manchmal sind Stack und Heap so angeordnet, dass sie gegeneinander wachsen
! Jede Region kann so gross wie möglich werden
staIc data
0000
stack
FFFF
heap
Stack und Heap
! Ein einfaches Modell dieser Bereiche hile, verschiedene Aspekte von Java zu verstehen
! Da Programme oe in einer Methode Variable deklarieren, die auf Instanzen verweisen, müssen wir sowohl den Heap als auch den Stack im Auge behalten
! In den folgenden Folien zeigen wir links den Heap und rechts den Stack
! Getrennt durch Linie
! Stack-Heap Diagramm
39Stack und Heap
! Wenn eine Methode aufgerufen wird, dann muss das neue Stack Frame genug Platz für alle (lokalen) Variablen haben
! Wenn eine Methode ferIg ist, dann kann ihr Stack Frame wiederverwendet werden
! Wenn eine ObjekInstanz geschaffen wird, dann brauchen wir Speicherplatz für alle Axribute (Zustandsvariablen)
! Dazu brauchen wir extra Platz für Java-interne Zwecke – Overhead den
wir nicht kontrollieren können
40Verweise (References) auf Objekte
! Für jede Instanz hält das Java System die Adresse der Instanz fest
! Diese Adresse (ggf. mit weiteren InformaIonen) wird in einer Variable gespeichert (wir sagen dazu auch “Reference variable” – die Variable enthält den Verweis, d.h. die Reference, auf ein Objekt).
! Nehmen wir an, eine Methode enthält die DeklaraIon
Rational r1 = new Rational(1, 2);
für die Klasse Rational (wie vorher eingeführt)
41
Verweise (References) auf Objekte
! Wenn die DeklaraIon
Rational r1 = new Rational(1, 2);
ausgeführt wird, brauchen wir Platz für eine neue Rational Instanz.
! Nehmen wir an diesen finden wir mit der Adresse 1000
42
Verweise (References) auf Objekte
! Wenn die DeklaraIon
Rational r1 = new Rational(1, 2);
ausgeführt wird, brauchen wir Platz für eine neue Rational Instanz.
! Nehmen wir an diesen finden wir mit der Adresse 1000
! Die Variable r1 wird im Stack Frame gespeichert und erhält den Wert 1000 – die Adresse der neuen Instanz
45
heap
den 2 num 1
1008 1004 1000
stack
1000
r1 FFFC
In Zeitlupe
skip simulation a
b c sum
FFFC FFF8 FFF4 FFF0 FFEC
1000 100C 1018 1030
TestRa=onal
stack
This object is a temporary value used only during the calculation.
1/2 + 1/3 + 1/6 = 1
public void run(){
Rational a = new Rational(1, 2);
Rational b = new Rational(1, 3);
Rational c = new Rational(1, 6);
Rational sum = a.add(b).add(c);
println(a + " + " + b + " + " + c + " = " + sum);
}
den 1 num 1 den 6 num 5 den 6 num 1 den 3 num 1 den 2 num 1
public Rational add(Rational r) {
return new Rational( this.num * r.den + r.num * this.den , this.den * r.den );
} 6
5
1 3 1 2
public Rational add(Rational r) {
return new Rational( this.num * r.den + r.num * this.den , this.den * r.den );
} 36
36
heap
1038 1034 1030 102C 1028 1024 1020 101C 1018 1014 1010 100C 1008 1004 1000
1000 100C this
r
FFE8 FFE4 FFE0 1000
100C 1024 1018
This stack frame is created for the run method.
This stack frame is created for the add method.
All objects are created in the heap.
TestRa=onal 1/2 + 1/3 + 1/6 = 1
public void run(){
Rational a = new Rational(1, 2);
Rational b = new Rational(1, 3);
Rational c = new Rational(1, 6);
Rational sum = a.add(b).add(c);
System.out.println(a + " + " + b + " + " + c + " = " + sum);
}
den 1 num 1 den 6 num 5 den 6 num 1 den 3 num 1 den 2 num 1
heap stack
a b c sum
FFFC FFF8 FFF4 FFF0 FFEC
1000 100C 1018 1030
1038 1034 1030 102C 1028 1024 1020 101C 1018 1014 1010 100C 1008 1004 1000
Explizite Pointer
den 1 num 1 den 6 num 5 den 6 num 1 den 3 num 1 den 2 num 1
heap stack
a b c sum
FFFC FFF8 FFF4 FFF0 FFEC
1000 100C 1018 1030
1038 1034 1030 102C 1028 1024 1020 101C 1018 1014 1010 100C 1008 1004 1000
! Die Skizze links zeigt den Zustand des Speichers am Ende der Methode run aus TestRational .
! Das Bild rechts zeigt den selben Zustand mit einem Pfeil (“Pointer”) anstelle der Adresse (Pointer Modell)
den 1 num 1 den 6 num 5 den 6 num 1 den 3 num 1 den 2 num 1
a b c sum
heap stack
Adressen vs. Pointer
den 1 num 1 den 6 num 5 den 6 num 1 den 3 num 1 den 2 num 1
heap stack
a b c sum
FFFC FFF8 FFF4 FFF0 FFEC
1000 100C 1018 1030
1038 1034 1030 102C 1028 1024 1020 101C 1018 1014 1010 100C 1008 1004 1000
Beide Skizzen (mit Adressen oder Pointern) zeigen den selben Zustand aber betonen unterschiedliche Aspekte.
– Adressen verdeutlichen dass Verweise (References) eine Zahl enthalten.
den 1 num 1 den 6 num 5 den 6 num 1 den 3 num 1 den 2 num 1
a b c sum
heap stack
– Das Pointer Modell betont die Beziehung zwischen Referenz(variable)
und Objekt(instanz).
Nicht-erreichbare Objekte
Das Pointer Modell macht klar das es keinen Verweis auf die Rational Instanz 5/6 gibt. Dieser Wert ist jetzt “Garbage”.
Das Java Laufzeitsystem führt von Zeit zu Zeit eine Speicherbereini- gung durch (“garbage collec9on”) – automaIsch
den 1 num 1 den 6 num 5 den 6 num 1 den 3 num 1 den 2 num 1
a b c sum
heap stack
Instanz die temporär gebraucht wurde aber jetzt unerreichbar ist.
Uebung: Stack-Heap Diagramm
public void run() {
Point p1 = new Point(0, 0);
Point p2 = new Point(200, 200);
Line line = new Line(p1, p2);
}
public class Point {
public Point(int x, int y) { cx = x;
cy = y;
}
. . . other methods appear here . . . private int cx;
private int cy;
}
Gegeben sind Klassen Point und Line wie folgt
public class Line {
public Line(Point p1, Point p2) { start = p1;
finish = p2;
}
. . . other methods appear here . . . private Point start;
private Point finish;
}
Zeichnen Sie ein Stack-Heap Diagramm für den Zustand
des Speichers bevor die Methode run auyört.
Lösung
100C finish
1000 start
cy 200 cx 200 cy 0 cx 0
heap stack
p1 p2 line
FFFC FFF8 FFF4 FFF0 1000
100C 1018
1020 101C 1018 1014 1010 100C 1008 1004 1000
finish start cy 200 cx 200 cy 0 cx 0
p1 p2 line
heap stack
Address Model Pointer Model
Basistypen vs Objekte
! Ein Basistyp Parameter wird nach den Regeln der Value Seman9cs übergeben.
! Ein Parameter für eine Referenzvariable folgt den Regeln der Reference Seman9cs.
! Nur auf den ersten Blick scheint es, dass Basistypen und Referenztypen unterschiedlich behandelt werden.
52
! Wenn wir eine Basistyp Variable als Parameter übergeben, dann kopieren wir den Wert der Variable
! Veränderungen in der Methode haben daher keine Wirkung
! Wenn wir ein Referenzvariable als Parameter übergeben, so kopieren wir die Referenz (mit der Adresse)
! Daher können wir die Instanz, die wir über die Adresse erreichen, verändern
! Entweder über Accessor/Mutator Methoden oder durch direkten Zugriff auf ein Axribut
53
Verantwortlichkeiten
! Sehen wir uns nochmal die Methode gcd (in Rational ) an:
/**
* Calculates the greatest common divisor * using Euclid's algorithm.
* @param x First integer * @param y Second integer
* @return The greatest common divisor of x and y */
int gcd(int x, int y) { int r = x % y;
while (r != 0) { x = y;
y = r;
r = x % y;
}
return y;
}
54int gcd(int x, int y) { int r = x % y;
while (r != 0) { x = y;
y = r;
r = x % y;
}
return y;
}
! Was für Annahmen macht diese Methode?
! Liefert sie immer das gewünschte Ergebnis?
! Was für Fälle sollten wir untersuchen?
! Schreiben Sie die Fälle auf, die Sie für "interesssant" halten
! Simulieren Sie die Ausführung
55
Verantwortlichkeiten
! Diese ImplementaIon des ggT erwartet
! x ≥ 0
! y > 0
! Wer ist dafür "verantwortlich"?
! Klient?
! (OK, hier ist der Klient die selbe Klasse Rational)
! Die Methode?
56
Verantwortlichkeiten
! (Teil)Lösung hier
gcd(Math.abs(x), Math.abs(y));
! Kommentar wäre nicht verfehlt
! gcd kann davon ausgehen dass x ≥ 0
! "x ≥ 0" ist eine Aussage die hier immer gilt
! Nicht gelöst ist dass y ≠ 0 sein muss
! Beispiel soll nicht überladen werden
57