Fernstudium Computational Engineering 2. Studienplansemester

Werbung
Beuth Hochschule
BEUTH HOCHSCHULE FÜR TECHNIK BERLIN
University of Applied Sciences
WISSENSCHAFTLICHE
WEITERBILDUNG
Fernstudium
Computational Engineering
2. Studienplansemester
Vertiefte Grundlagen des CAE
Modul 05 / Kurseinheit 123
Elektronische Datenverarbeitung II
Prof. Dipl. math. Uwe Stephan
12.2009
3
© Beuth Hochschule für Technik Berlin – Fernstudieninstitut
3.1
Die Klasse – der grundlegende Begriff
Lernziele:
Nach dem Durcharbeiten dieses Kapitels sollen Sie:
 die grundsätzliche Struktur einer Klasse kennen,
 öffentliche (public) und private (private) Komponenten einer Klasse
sinnvoll einsetzen können,
 Konstruktoren und einfache Klassenoperationen schreiben können,
 die Begriffe der ref-Variablen und der non-ref-Variablen und ihre
technische Realisierung in Java kennen.
3.1
Die Philosophie der Klassenstruktur
Der Autor hat seine Beispiele für diesen Kurs so abgespeichert, dass jedes
Beispiel in einem eigenen Verzeichnis abgelegt ist. Diese Verzeichnisse
heißen bsp1, bsp2, bsp3 .... und sind alle Unterverzeichnisse eines Verzeichnisses fsi_java:
fsi_java
+-----------+-----------+-----------+----------bsp1
bsp2
bsp3
Sie können das Verzeichnis fsi_java als Standard-Vorgabe einstellen.
Wählen Sie dazu im Menü Configure  Options und markieren Sie in der
Auswahl der diversen Gebiete „Directories“. Dann können Sie das „Default
Project Directory“ einstellen.
Erzeugen Sie jetzt (innerhalb WINDOWS) ein neues Verzeichnis bsp3 für
das nächste Beispiel und kopieren Sie das komplette Unterverzeichnis
simple_io in dieses Verzeichnis. Dann erzeugen Sie im JCreator ein neues
Projekt mit dem Menüpunkt File  New  Project. Geben Sie diesem
neuen Projekt den Namen bsp3 (es ist am einfachsten, wenn Projektname
und Verzeichnisname identisch sind) und achten Sie darauf, dass das Verzeichnis bsp3 Projektverzeichnis wird (Einstellung im Feld „Location“ während der Erzeugung des Projekts).
Jetzt kommen wir zum eigentlichen Thema. Wir wollen Punkte der Ebene
verarbeiten, und letztendlich soll das Ziel unseres Programms die Steuerung
eines sehr einfachen Roboterarmes sein. Dieses Ziel haben wir nur vor Augen – so weit wird die Realisierung hier nicht gehen. Der Roboterarm ist um
eine Achse drehbar, seine Position wird durch einen Winkel phi beschrieben. Dieser Winkel muss aus technischen Gründen stets zwischen -360° und
+360° liegen (sonst würden Verbindungskabel überdehnt oder würden
reißen). Der Roboterarm kann auch ausgefahren werden, seine Länge r muss
stets zwischen 0,5 m und 2,0 m liegen. In einem Programm soll die Lage
eines Roboterarmes durch jeweils einen Punkt beschrieben werden.
Computational Engineering
Elektronische Datenverarbeitung II
KE 123
3.2
© Beuth Hochschule für Technik Berlin – Fernstudieninstitut
Klassenkonzept:
Fehler vermeiden
Datenkomponenten
schützen
12.2009
Ziel des Klassenkonzepts ist es jetzt, den Programmierer bei der Vermeidung von Fehlern weitgehend zu unterstützen. In den Anfängen des Programmierens versuchte man, Fehler zu vermeiden, indem man viele gute
Regeln aufstellte und hoffte, die Programmierer würden sich an diese
Regeln halten. Heutzutage setzt man diese Regeln rigoros mit Hilfe passender Sprachen durch. Java ist eine solche Sprache. Unsere Anforderungen
hier lauten:
 Eine Variable, die einen Punkt beschreibt (Typ: CPunkt), enthält zwei
Komponenten r und phi.
 Die Werte dieser Variablen müssen immer zwischen 0.5 und 2.0 bzw.
zwischen –360.0 und +360.0 liegen.
Die erste Forderung legt nahe, dass der Typ CPunkt eine Verbund-Struktur
(englisch: record) hat. Beachten Sie dazu den Abschnitt 5.1 der Kurseinheit
115. Die zweite Forderung hat zur Folge, dass der „normale“ Programmierer keinen Zugriff auf die Komponenten r und phi einer Variablen vom
Typ CPunkt hat. Hätte er diesen Zugriff, könnten wir nicht mehr garantieren, dass die Werte nur in den geforderten Intervallen liegen.
Ansatz:
private Daten
Datenkapsel:
Verbund (record)
Zugriff nur durch
Klassenoperationen
KE 123
Im Abschnitt 13.5 der Kurseinheit 115 hatten wir bereits den Lösungsansatz
der 80-er Jahre des vorigen Jahrhunderts skizziert: Modulare Programmierung und die Verwendung von Paketen. Ein Paket hatte einen privaten und
einen öffentlichen Teil. Der „normale“ Programmierer hatte nur Zugriff auf
den öffentlichen Teil, die Daten im privaten Teil waren geschützt. Dieses
Konzept, das in der Modularen Programmierung für das Paket galt,
übertragen wir jetzt auf die einzelne Variable.
Der Typ einer Variablen wird jetzt durch eine Klasse (class) beschrieben:
 Eine Variable eines solchen Klassentyps hat stets eine Verbund-Struktur
(record-Struktur), auch wenn sie mal nur eine Komponente haben sollte.
 Die Daten-Komponenten einer solchen Variablen sind im Allgemeinen
als private deklariert, d.h. ein Anwendungsprogrammierer, der
Variablen dieses Typs verwendet, kann nicht auf die Komponenten
zugreifen.
 Bei der Erzeugung einer Variablen wird durch ein besonderes
Unterprogramm, einen sogenannten Konstruktor, sichergestellt, dass die
Komponenten genau definierte, gültige Werte haben.
 Die Veränderung dieser Werte ist nur durch Aufruf von speziellen
Unterprogrammen möglich, die Teil der Klasse sind. Der englische
Fachausdruck für diese Unterprogramme ist „member-functions“, der
deutsche Ausdruck ist „Klassenoperationen“. Diese Unterprogramme
haben Zugriff auf die Komponenten.
Eine Bemerkung zur Realität: Wenn bei der Programmierung der Klassenoperationen Fehler gemacht werden, kann die Korrektheit der Daten
natürlich auch nicht garantiert werden. Wenn aber einmal (nach Tests und
Fehlerkorrektur) die Klassenoperationen fehlerfrei sind, kann der Anwendungsprogrammierer keine Fehler in den Daten mehr verursachen, die die
obigen Bedingungen (Werte zwischen 0.5 und 2.0 bzw. zwischen –360.0
und +360.0) verletzen.
Elektronische Datenverarbeitung II
Computational Engineering
12.2009
3.3
© Beuth Hochschule für Technik Berlin – Fernstudieninstitut
Jetzt zur technischen Realisierung in Java. Wir erzeugen mit File  New 
File eine neue Textdatei bsp3.java und beschreiben dort als Erstes nur die
Datenstruktur des neuen Typs:
class CPunkt {
private double r, phi;
}
Beachten Sie beim Eintippen, wie der Editor Sie unterstützt. Sobald Sie die
öffnende Klammer { getippt haben, fügt der Editor automatisch die
schließende Klammer } ein.
Anschließend definieren wir einen Konstruktor. Konstruktoren haben stets
denselben Namen wie die Klasse, sehen aus wie functions in C, haben
jedoch keinen return-Typ. Wir nehmen an, dass am Anfang r immer den
Wert 0.5 und phi den Wert 0.0 haben soll, sofern nichts weiter gesagt ist.
Damit sieht die Klasse so aus:
class CPunkt {
private double r, phi;
Konstruktionen:
Name=Klassenname
kein return-Typ
CPunkt () { // default-constructor
r = 0.5 ; phi = 0.0 ;
}
}
Natürlich sollte man die Werte –360.0, +360.0, 0.5 und 2.0 durch
Konstanten beschreiben, wir wollen dieses erste Beispiel jedoch nicht
überfrachten mit Details.
Wir wollen gleich noch eine Klassenoperation definieren, die den Inhalt
einer Variablen des Typs CPunkt zeigt. Die gesamte Klasse lautet dann
class CPunkt {
private double r, phi;
CPunkt () { // default-constructor
r = 0.5 ; phi = 0.0 ;
}
void zeigePunkt () {
System.out.println( "r = "
+ FormattedStrings.strOfDouble (r,5,2)
+ "; phi = "
+ FormattedStrings.strOfDouble (phi,6,1) );
}
}
Beim Konstruktor und bei der Klassenoperation zeigePunkt bemerken Sie
bereits: Unterprogramm in Java haben dieselbe äußere Struktur wie in C.
Eine fehlende Parameterliste wird jedoch nicht durch void, sondern durch
eine leere Liste angezeigt.
Computational Engineering
Elektronische Datenverarbeitung II
KE 123
3.4
© Beuth Hochschule für Technik Berlin – Fernstudieninstitut
12.2009
3.2 Variablen und Anweisungen in Java
Wir wollen gleich zeigen, dass mit dieser Klassendefinition ein neuer Typ
definiert wurde. Fügen Sie in dieselbe Quelldatei bsp3.java ein Hauptprogramm ein, indem Sie zunächst die Klasse dafür beschreiben (den Klassennamen bsp3 sollten Sie unbedingt von Hand tippen (nicht kopieren), da der
JCreator sich diesen Namen sofort merkt und spätere Änderungen nicht
mehr zulässt):
import java.io.* ;
import simple_io.* ;
class bsp3 {
// enthält das Hauptprogramm
}
Danach können Sie die Definition von main aus einer anderen Quelle
kopieren. Die gesamte Quelle sollte dann so aussehen:
import java.io.* ;
import simple_io.* ;
class bsp3 {
// enthält das Hauptprogramm
public static void main ( String[] args )
throws java.io.IOException
{
System.out.println( "Beispiel 3 beginnt ..." ) ;
}
}
class CPunkt {
.........
}
Übersetzen Sie das Projekt und führen Sie es aus.
Als nächstes wollen wir eine Variable vom Typ CPunkt definieren. Dabei
ist zu beachten, dass in Java andere Regeln für den Umgang mit Variablen
gelten. Wir fassen diese Regeln kurz zusammen:
non-refVariablen
KE 123
 Es gibt so genannte „non-ref-Variablen“ (Java: „Variables of Primitive
Type“). Sie haben dieselbe Bedeutung wie in C, wir können sie uns als
Speicherkästchen vorstellen. Atomare Typen haben stets non-refVariablen. Dies sind die folgenden Typen:
Elektronische Datenverarbeitung II
Computational Engineering
3.5
© Beuth Hochschule für Technik Berlin – Fernstudieninstitut
12.2009
Typ-Name
Anzahl Bits
Wertebereich
byte
8
-128 bis 127
short
16
-32768 bis 32767
int
32
-2147483648 bis 2147483647
long
64
ca. –1019 bis +1019
char
16
alle Unicode Zeichen
float
32
siehe ANSI/IEEE Standard 754
double
64
siehe ANSI/IEEE Standard 754
bool
true
false
Wir bemerken insbesondere, dass Java im Gegensatz zu C einen eigenen
Typ bool für logische Werte hat.

Alle anderen Variablen (Klassenvariablen, Felder = arrays) sind so genannte ref-Variablen (Java: „Variables of Reference Type“). Wir erläutern diesen Begriff an einer Variablen vom Typ CPunkt.
ref-Variablen
Im Hauptprogramm main definieren wir
CPunkt p1;
p1 ist damit eine ref-Variable vom Typ CPunkt, d.h. p1 ist Zeiger auf eine
Variable vom Typ CPunkt. Das „ref“ steht als Abkürzung für „reference“,
und reference ist ein anderes Wort für Adresse, Zeiger oder Bezug. Java
kennt den Begriff der Adresse (der reference), erlaubt aber nicht, Zeiger
(wie in C) vom Programm her zu verändern.
Bildlich haben wir nach der obigen Definition also folgende Situation: Die
Variable p1 existiert, hat aber keinen Inhalt (genauer: Sie enthält eine „null
reference“, einen Zeiger ohne Wert).
p1
Wir müssen die eigentliche Variable, also das Objekt vom Typ CPunkt, erst
noch erzeugen. Dies geschieht durch Aufruf eines Konstruktors dieser
Klasse hinter dem Schlüsselwort new:
p1 = new CPunkt ();
Computational Engineering
Elektronische Datenverarbeitung II
KE 123
3.6
© Beuth Hochschule für Technik Berlin – Fernstudieninstitut
12.2009
Danach haben wir:
p1
Bild 3.1:
r
0,5
phi
0,0
Referenz- und Objektvariable
Um die beiden Variablen im Bild 3.1 sprachlich unterscheiden zu können,
vereinbaren wir: Das kleine Speicherkästchen ist die ref-Variable p1 vom
Typ CPunkt. p1 enthält einen Zeiger auf die Objektvariable (sie wird im
Bild 3.1 durch das große Kästchen dargestellt). Die Objektvariable wird
auch „eine Instanz der Klasse CPunkt“ genannt.
Wir lassen uns diese Werte im Programm anzeigen und rufen die Klassenoperation zeigePunkt() für die Variable p1 auf. Dafür (für den Aufruf
einer Klassenoperation, die mit einer Variablen dieser Klasse arbeiten soll)
gibt es eine besondere Notation, die an den Zugriff auf Komponenten eines
Verbundes erinnert (siehe Abschnitt 5.1 der Kurseinheit 115):
p1 . zeigePunkt();
Diese Anweisung ist ein Unterprogrammaufruf. p1 ist das aktuelle Objekt
dieses Aufrufs, zeigePunkt() ist das aufgerufene Unterprogramm oder die
aufgerufene Klassenoperation.
3.3 Weitere Konstruktoren und Klassenoperationen.
Eine Klasse kann mehrere Konstruktoren enthalten. Wollen wir (beim Entwerfen und Schreiben der Klasse) dem Anwendungsprogrammierer die
Möglichkeit geben, eine Variable des Typs CPunkt gleich am Anfang mit
eigenen Werte zu belegen, so können wir einen Konstruktor mit Parametern
definieren. In den Anweisungen des Konstruktors müssen wir dafür sorgen,
dass zum Schluss gültige Werte in r und phi stehen oder dass die Konstruktorausführung mit einer Fehlermeldung abgebrochen wird. Wir haben
uns hier für die schlechtere Lösung, die automatische Korrektur entschieden, da die Fehlerbehandlung in Java erst später im Kurs behandelt wird.
CPunkt ( double r_anf, double phi_anf )
{
// ein INIT-constructor
if ( (0.5 <= r_anf ) && ( r_anf <= 2.0 ) )
r = r_anf ;
else r = 0.5 ;
if ( (-360.0 <= phi_anf) && (phi_anf<=360.0))
phi = phi_anf;
else phi = 0.0 ;
}
KE 123
Elektronische Datenverarbeitung II
Computational Engineering
3.7
© Beuth Hochschule für Technik Berlin – Fernstudieninstitut
12.2009
Sie bemerken: Die Syntax der Anweisungen ist wie in C, der Operator &&
steht für das logische UND.
Wir definieren eine zweite Variable p2 und belegen Sie mit Anfangswerten
1.2 und –90°.
Das Hauptprogramm sieht z. Z. so aus:
public static void main ( String[] args )
throws java.io.IOException
{
System.out.println( "Beispiel 3 beginnt ..." ) ;
CPunkt p1, p2 ;
p1 = new CPunkt (); p1 . zeigePunkt();
p2 = new CPunkt ( 1.2 , -90.0 );
p2 . zeigePunkt();
}
Beachten Sie: Im Programm müssen wir die konstanten Werte mit einem
Dezimalpunkt schreiben, der Java-Compiler erwartet es so. Bei der Eingabe
tippen Sie ein Dezimalkomma, sofern Sie das Programm in Deutschland
ausführen, da wir in den Routinen von simple_io auf das Land Bezug
nehmen, in dem das Programm aktuell ausgeführt wird. Bei der Ausgabe
sollten Sie (bei Ausführung in Deutschland) auch Dezimalkommata sehen.
Wie kann nun der Compiler die beiden Konstruktoren unterscheiden? In den
klassischen Programmiersprachen (also auch in C) gilt, dass Unterprogramme durch ihren Namen identifiziert werden, dass daher alle Namen von
Unterprogrammen verschieden sein müssen. In objektorientierten Programmiersprachen (wie C++, Java) gilt, dass ein Unterprogramm durch seine
Klassenzugehörigkeit und innerhalb der Klasse durch seine Signatur bestehend aus Name und Liste der Parametertypen identifiziert wird. CPunkt()
ist also ein anderer Konstruktor als CPunkt (double, double).
Signatur
eines
Unterprogramms
Wir entwerfen jetzt noch zwei Klassenoperation fahre_aus, um den Roboterarm um eine bestimmte Länge auszufahren und drehe_um, um den
Arm um einen bestimmten Winkel zu drehen.
void fahre_aus ( double delta_r ) {
double neu_r = r + delta_r ;
if ( ( neu_r < 0.5 ) || (2.0 < neu_r ) )
return ; // Fehlerbehandlung können wir
// noch nicht
r = neu_r ;
}
void drehe_um (double delta_phi) {
if ( ( delta_phi < -360.0)
|| ( 360.0 < delta_phi) )
return ;
double drehwinkel = delta_phi ;
if ( phi + drehwinkel < -360.0 )
drehwinkel = 360.0 + drehwinkel ;
if ( phi + drehwinkel > 360.0 )
drehwinkel = drehwinkel - 360.0 ;
System.out.println( "Technische Drehung um " +
FormattedStrings.strOfDouble (drehwinkel,6,1) );
phi += drehwinkel ;
}
Computational Engineering
Elektronische Datenverarbeitung II
KE 123
3.8
© Beuth Hochschule für Technik Berlin – Fernstudieninstitut
12.2009
Das Hauptprogramm ergänzen wir so, dass man wiederholt Bewegungsdaten am Bildschirm eingeben kann und die neue Position dann gezeigt
wird.
public static void main ( String[] args )
throws java.io.IOException
{
System.out.println( "Beispiel 3 beginnt ..." ) ;
CPunkt p1, p2 ;
double delta_r, delta_phi ;
p1 = new CPunkt (); p1 . zeigePunkt();
p2 = new CPunkt ( 1.2 , -90.0 );
System.out.print ("Roboterarm p2 bei ");
p2 . zeigePunkt();
do {
delta_r = SimpleInput.readDouble (
"Arm ein/ausfahren um : ");
delta_phi = SimpleInput.readDouble (
"Arm drehen um : ");
System.out.println ("");
p2 . fahre_aus ( delta_r );
p2 . drehe_um ( delta_phi );
System.out.print ("Roboterarm p2 bei ");
p2 . zeigePunkt();
} while ( (delta_r != 0.0)||(delta_phi != 0.0) );
}
Übersetzen Sie das Programm und führen Sie es aus. Geben Sie im Test
dann mehrfach positive Winkel ein und beobachten Sie, wie das System bei
zu großem Gesamtwinkel technisch eine negative Drehung durchführt, um
den Bereich –360° bis +360° nicht zu verlassen.
3.4
Java und C: Gemeinsamkeiten und Unterschiede.
An den obigen Beispielen sehen Sie bereits, dass die Syntax der einzelnen
Anweisungen in Java („Programmieren im Kleinen“) der Syntax von C
ähnlich ist. Dennoch gibt es einige wesentliche Unterschiede, die in späteren
Kapiteln behandelt werden. Wir können die Regeln kurz zusammenfassen:
KE 123

Lokale atomare Variablen (also vom Typ byte, short, int, long, char,
float, double und bool) werden wie in C definiert (wobei C den Typ
bool nicht kennt).

Arrays haben in Java eine völlig andere Struktur, die in einem
besonderen Kapitel behandelt wird.

Kontrollstrukturen (Sequenz, Wiederholungen, Fallunterscheidungen)
haben in Java dieselbe Struktur und dieselben Schlüsselwörter wie in C.

Java kennt den Begriff der Adresse: Er steckt im Begriff der refVariablen. Java stellt jedoch keine Operatoren zur Veränderung von
Adressen zur Verfügung.
Elektronische Datenverarbeitung II
Computational Engineering
12.2009
© Beuth Hochschule für Technik Berlin – Fernstudieninstitut

In Unterprogrammen werden alle Parameter als Vorgabeparameter
behandelt, es gibt weder den Adresse-von-Operator (in C: & wie in & v)
noch den Zugriff-über-Adresse-Operator (in C: * wie in *p). Parameter
atomaren Typs (also vom Typ byte, short, int, long, char, float,
double und bool) können daher nie Rückgabeparameter sein.

ref-Variablen enthalten Adressen von Objekten. Tritt eine ref-Variable
als Parameter auf, so kann das zugehörige Objekt durch den
Unterprogrammaufruf verändert werden. In diesem Sinne können auch
Java-Unterprogramme Rückgabeparameter haben.
Computational Engineering
Elektronische Datenverarbeitung II
3.9
KE 123
3.10
© Beuth Hochschule für Technik Berlin – Fernstudieninstitut
3.5
12.2009
Übungsaufgaben
Aufgabe 1
Nennen Sie die drei Arten von Elementen, die als Teil einer Klasse auftreten
können.
Aufgabe 2:
Wodurch identifiziert der Java-Compiler einzelne Klassenoperationen oder
Konstruktoren?
Aufgabe 3:
Nennen Sie alle atomaren Typen von Java.
Aufgabe 4:
a) Was sind non-ref-Variablen, was sind ref-Variablen?
b) Welche Typen haben non-ref-Variablen, welche haben refVariablen?
c) Wie erzeugt man im Programm ein Objekt (eine Instanz) zu einer
gegebenen Klasse?
KE 123
Elektronische Datenverarbeitung II
Computational Engineering
12.2009
© Beuth Hochschule für Technik Berlin – Fernstudieninstitut
3.11
3.6 Lösungen zu den Übungsaufgaben
Aufgabe 1:
Eine Klasse besteht aus Datenelementen, Konstruktoren und Klassenoperationen.
Aufgabe 2:
Jede Klassenoperation und jeder Konstruktor wird durch seine Signatur
innerhalb der Klasse eindeutig identifiziert. Die Signatur besteht aus dem
Namen dieses Unterprogramms und der Liste der Parametertypen.
Aufgabe 3:
Die atomaren Typen sind byte, short, int, long, char, float, double
und bool.
Aufgabe 4:
a) non-ref-Variablen enthalten stets einen Wert des zugehörigen Typs.
ref-Variablen enthalten stets einen null-Zeiger (null reference) oder
einen Zeiger auf ein Objekt des zugehörigen Typs.
b) Alle atomaren Typen (siehe Aufgabe 3) erzeugen non-ref-Variablen.
Alle Klassentypen (ob selbst geschrieben oder aus der JavaBibliothek übernommen) und alle Felder (arrays) erzeugen refVariablen.
c) Hinter dem Schlüsselwort new ruft man einen Konstruktor der
Klasse auf.
Computational Engineering
Elektronische Datenverarbeitung II
KE 123
Herunterladen