Kapitel 3.3 - Fachgebiet Programmiersprachen und Ãbersetzer
Kapitel 3.3 - Fachgebiet Programmiersprachen und Ãbersetzer
Kapitel 3.3 - Fachgebiet Programmiersprachen und Ãbersetzer
Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.
YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.
<strong>Programmiersprachen</strong> <strong>und</strong> Übersetzer<br />
Sommersemester 2011<br />
10. Juli 2011
Einführung in die objektorientierte Programmierung<br />
◮ Einführung 1967 durch die Sprache Simula 67 von O.J. Dahl<br />
<strong>und</strong> K. Nygaard. In dieser Sprache wurden erstmalig Klassen<br />
<strong>und</strong> Objekte eingeführt.<br />
◮ Ende der 60er Jahre entstand im Rahmen des<br />
Dynabook-Projekts am Palo Alto Research Center von Xerox<br />
unter der Leitung von Alan Kay die Programmiersprache<br />
Smalltalk — eine rein objektorientierte Programmiersprache.<br />
◮ Die erste Version war Smalltalk 72 (1972), die letzte, auch<br />
heute noch aktuelle Version ist Smalltalk 80<br />
(Adele Goldberg, 1980).
Wichtige Konzepte in Smalltalk<br />
Das gesamte Smalltalk-Environment ist in Smalltalk geschrieben.<br />
Der Quellcode des gesamten Systems steht zur Verfügung <strong>und</strong><br />
kann gelesen <strong>und</strong> bei Bedarf geändert werden.<br />
Wichtige Punkte:<br />
◮ Bitmap-Graphik<br />
◮ Verwendung der Maus als Eingabegerät<br />
◮ Fenstersystem mit allgemeiner Menüsteuerung<br />
◮ Byte-Code für eine virtuelle Maschine<br />
◮ automatisches Garbage Collection<br />
◮ inkrementelle Programmentwicklung
Charakteristika objektorientierter Sprachen:<br />
1. die dynamische Auswahl der Methoden (dynamic lookup).<br />
2. die Abstraktion (abstraction) vom internen Aufbau der<br />
Objekte.<br />
3. die Möglichkeit zur Bildung von Untertypen (subtyping).<br />
4. die Vererbung (inheritance) von Eigenschaften
Objekte<br />
◮ Objekte sind zur Laufzeit in einem objektorientierten System<br />
existierende Einheiten. Sie belegen Speicherplatz <strong>und</strong> haben<br />
daher eine assoziierte Adresse.<br />
◮ Ein Objekt hat einen zugeordneten Datenbereich, in dem der<br />
momentane Zustand des Objekts dargestellt wird. Dieser<br />
Zustand wird durch Eigenschaften (Attribute) des Objekts<br />
repräsentiert. Die momentanen Werte dieser Eigenschaften<br />
sind in lokalen Variablen des Objekts, den sogenannten<br />
Instanzenvariablen, gespeichert.
◮ Einem Objekt sind Funktionen, sogenannte Methoden<br />
(Aktionen) zugeordnet, die das Objekt ”<br />
beherrscht“ <strong>und</strong><br />
ausführen kann.<br />
◮ Abstraktionsbarrieren kapseln den Zustand eines Objekts ein.<br />
Der Zustand (Werte der Instanzenvariablen) kann<br />
üblicherweise nur über eine der zugeordneten Methoden<br />
verändert oder nach außen hin sichtbar gemacht werden<br />
(Prinzip des Information Hiding).<br />
◮ Um eine Methode auf ein Objekt anzuwenden, wird in<br />
objektorientierten Systemen häufig ein Nachrichtenschema<br />
verwendet. Man schickt einem Objekt eine Nachricht. Dies<br />
führt dann dazu, dass eine der Methoden ausgewählt <strong>und</strong> auf<br />
das Objekt angewendet wird.
Klassen<br />
◮ Eine Klasse definiert eine Menge möglicher, gleichartiger<br />
Objekte (Instanzen der Klasse), die alle die gleichen<br />
Attribute <strong>und</strong> Methoden besitzen. Eine Klasse kann als ein<br />
vom Benutzer definierter Datentyp interpretiert werden<br />
(abstrakter Datentyp).<br />
◮ Für jede Klasse existiert ein Mechanismus, der es gestattet,<br />
Objekte der Klasse zu erzeugen <strong>und</strong> wieder zu vernichten. In<br />
vielen Systemen geschieht dies dynamisch, also zur Laufzeit<br />
des Systems. Häufig wird auch ein Garbage Collector<br />
verwendet, um den frei gewordenen Speicher zu sammeln<br />
◮ Es gibt sogenannte abstrakte Klassen, von denen keine<br />
Objekte erzeugt werden. Sie dienen in Verbindung mit der<br />
Vererbung im wesentlichen nur zur Sammlung gemeinsamer<br />
Methoden <strong>und</strong> Attribute.
Dynamische Auswahl der Methoden<br />
◮ Wenn einem Objekt eine Nachricht geschickt wird, dann<br />
entscheidet das Objekt, welche Methode ausgewählt wird.<br />
◮ Unterschiedliche Objekte, die etwa nacheinander durch eine<br />
Variable x repräsentiert werden, reagieren auf die selbe<br />
Nachricht unterschiedlich.<br />
◮ Wichtig daran ist, dass die auszuführende Methode<br />
dynamisch, also zur Laufzeit des Programms, gewählt werden<br />
kann.
Beispiel<br />
Es sollen Funktionen implementiert werden, die für alle<br />
Angehörigen der Universität Informationen darstellen oder das<br />
Gehalt auszahlen. In konventioneller Form würde man zwei<br />
Funktionen etwa der folgenden Form schreiben:<br />
info(x) =<br />
case type(x) of<br />
Professor:<br />
Assistent:<br />
end;<br />
end;<br />
Hilfskraft:<br />
["Display Info über Professor"];<br />
["Display Info über Assistent"];<br />
["Display Info über Hilfskraft"];
<strong>und</strong><br />
bezahle(x) =<br />
case type(x) of<br />
Professor:<br />
Assistent:<br />
end;<br />
end;<br />
Hilfskraft:<br />
["zahle Professor"];<br />
["zahle Assistent weniger"];<br />
["zahle Hilfskraft viel weniger"];
In einem objektorientierten Programm sind die Funktionen an die<br />
Daten gekoppelt, auf die sie angewendet werden.<br />
In unserem Beispiel hätten wir die Klasse der Professoren, der<br />
Assistenten <strong>und</strong> der Hilfskräfte <strong>und</strong> jede Klasse würde die zwei<br />
Methoden info <strong>und</strong> bezahle enthalten. Also etwa:<br />
class Professor =<br />
info = ["Display Info über Professor"];<br />
bezahle = ["zahle Professor"];<br />
end;
<strong>und</strong><br />
class Assistent =<br />
info = ["Display Info über Assistent"];<br />
bezahle = ["zahle Assistent weniger"];<br />
end;<br />
class Hilskraft =<br />
info = ["Display Info über Hilfskraft"];<br />
bezahle = ["zahle Hilfskraft viel weniger"];<br />
end;
Vergleich funktionale - prozedurale Vorgehensweise:<br />
Operation Professor Assistent Hilfskraft<br />
info info Professor info Assistent info Hilfskraft<br />
bezahle bezahle Professor bezahle Assistent bezahle Hilfskraft<br />
◮ In konventionellen <strong>Programmiersprachen</strong> wird der Code<br />
zeilenweise in einer Funktion gruppiert, die auf allen hier<br />
auftretenden Arten von Daten arbeitet.<br />
◮ In objektorientierten <strong>Programmiersprachen</strong> wird der Code<br />
spaltenweise gebündelt, in dem die einzelnen Funktionsteile<br />
mit den Daten, auf denen sie arbeiten sollen, gruppiert werden.
Abstraktion<br />
In diesem Kontext bedeutet Abstraktion das Verstecken von<br />
Implementationsdetails einer Programmeinheit, so dass auf die<br />
Interna nur über ein spezielles Interface zugegriffen werden kann.<br />
Beispiel<br />
Man stelle sich die folgenden zwei abstrakten Datentypen für<br />
Warteschlangen (queue) <strong>und</strong> für Prioritätswarteschlangen (priority<br />
queue) vor.<br />
(hier: ML-Notation für abstrakte Datentypen. Der Einfachheit<br />
halber sollen die abgespeicherten Objekte Integer-Zahlen sein.)
Zunächst die Definition einer Warteschlange. Sie wird als Liste mit<br />
den üblichen Operationen dargestellt.<br />
exception Empty;<br />
abstype queue = Q of int list<br />
with<br />
fun mk Queue() = Q(nil)<br />
and is empty(Q(l)) = l=nil<br />
and add(x,Q(l)) = Q(l@[x])<br />
and first(Q(nil)) = raise Empty | first(Q(x::l)) = x<br />
and rest(Q(nil)) = raise Empty | rest(Q(x::l)) = Q(l)<br />
and length(Q(nil)) = 0 | length(Q(x::l)) = 1+ length(Q(l))<br />
end;
Die Prioritätswarteschlange ist eine Warteschlange, in der beim<br />
Entfernen eines Elementes immer das kleinste ausgewählt wird.<br />
Im Beispiel wird das dadurch erreicht, dass die Objekte in der Liste<br />
immer aufsteigend sortiert gehalten werden.<br />
abstype pqueue = Q of int list<br />
with<br />
fun mk PQueue() = Q(nil)<br />
and is empty(Q(l)) = l=nil<br />
and add(x,Q(l)) =<br />
let fun insert(x,nil) = [x:int]<br />
| insert(x,y::l) =<br />
if x
◮ Als Interface eines abstrakten Datentyps (die Signatur) wird<br />
üblicherweise die Liste der öffentlichen Funktionen <strong>und</strong> deren<br />
Typen bezeichnet.<br />
◮ In unserem Beispiel sind beide Interfaces bis auf die<br />
Typ-Namen queue <strong>und</strong> pqueue identisch.<br />
◮ In konventionellen <strong>Programmiersprachen</strong> kann diese<br />
Korrespondenz bei abstrakten Datentypen nicht ausgenutzt<br />
werden.<br />
◮ Fünf Funktionen haben eine identische Implementation!<br />
◮ In objektorientierten <strong>Programmiersprachen</strong> kann man den<br />
Vererbungsmechanismus benutzen, um etwa<br />
Prioritäts-Warteschlangen aus der Definition der<br />
Warteschlangen durch Umdefinition der add-Funktion <strong>und</strong><br />
Mitbenutzung der anderen Funktionen zu definieren.
Untertypen<br />
◮ Das Bilden von Untertypen erzeugt eine Relation auf den<br />
Typen, die es erlaubt, Werte eines Typs anstelle von Werten<br />
eines anderen Typs zu benutzen.<br />
◮ Ist X Untertyp von Y , geschrieben X
Vererbung<br />
◮ Vererbung ist ein Konzept, das es erlaubt, neue Objekte durch<br />
Erweiterung bereits existierender Objekte zu definieren.<br />
◮ Durch Vererbung werden Attribute <strong>und</strong> Methoden einer<br />
Klasse X an eine andere Klasse Y weitergegeben. Die Klasse Y<br />
ist dann eine Unterklasse von X bzw. die Klasse X ist<br />
Oberklasse der Klasse Y.<br />
◮ In einer Unterklasse können Attribute <strong>und</strong> Methoden zu den<br />
geerbten der Oberklasse hinzugefügt werden. Es können aber<br />
auch vererbte Methoden neu definiert <strong>und</strong>/oder implementiert<br />
werden.<br />
◮ Bei einer einfachen Vererbung hat jede Klasse höchstens eine<br />
Oberklasse. Dies führt zu einer hierarchischen Anordnung der<br />
Klassen.
◮ Ist eine mehrfache Vererbung erlaubt, so hat eine Klasse<br />
mehrere Oberklassen.<br />
(Aber Zyklen <strong>und</strong> Namenskonflikte möglich!)<br />
◮ Vom Prinzip her könnte man Vererbung durch Duplizieren von<br />
Code realisieren.<br />
Bemerkung<br />
Wichtig ist in diesem Zusammenhang der Unterschied zwischen<br />
Untertyp-Bildung <strong>und</strong> Vererbung. Untertypen bilden eine Relation<br />
auf den Typen (Interfaces), Vererbung dagegen bildet eine Relation<br />
auf den Implementationen.
Beispiel<br />
Es soll ein Programm geschrieben werden, das mit den<br />
Datenstrukturen stack, queue <strong>und</strong> dequeue arbeitet.<br />
stack: Eine Datenstruktur mit Einsetz- <strong>und</strong> Löschoperation,<br />
so dass das zuerst eingesetzte Objekt als letztes<br />
entfernt wird (first-in, last-out).<br />
queue: Eine Datenstruktur mit Einsetz- <strong>und</strong> Löschoperation,<br />
so dass das zuerst eingesetzte Objekt als erstes<br />
entfernt wird (first-in, first-out).<br />
dequeue: Eine Datenstruktur mit zwei Einsetz- <strong>und</strong> zwei<br />
Löschoperationen. Eine dequeue ist eine Liste, bei der<br />
sowohl am Anfang als auch am Ende Objekte<br />
eingesetzt oder entfernt werden können.
◮ Die Datenstruktur dequeue kann sowohl die Aufgaben der<br />
Datenstrukturen stack als auch queue übernehmen, also<br />
kann man zunächst die Klasse dequeue implementieren <strong>und</strong><br />
dann die Klassen stack <strong>und</strong> queue als Unterklassen von<br />
dequeue definieren, indem man die geerbten Methoden zum<br />
Einsetzen <strong>und</strong> Löschen umbenennt bzw. überschreibt.<br />
◮ Obwohl stack <strong>und</strong> queue Unterklassen von dequeue sind,<br />
bilden sie keinen Untertyp von dequeue.<br />
◮ Dagegen kann man in jedem Kontext, in dem man etwa ein<br />
Objekt der Klasse stack bzw. queue benutzt, ohne<br />
Schwierigkeiten auch ein Objekt der Klasse dequeue<br />
benutzen. Folglich ist dequeue Untertyp sowohl von stack<br />
als auch von queue.
Kurze Einführung in Smalltalk<br />
◮ In Smalltalk ist alles ein Objekt.<br />
◮ es gibt keine primitiven Typen, keine inneren Klassen usw.<br />
◮ Bindungen geschehen nur über Referenzen<br />
◮ jedes Objekt ist Instanz einer Klasse<br />
◮ jede Klasse ist ein Objekt, also Instanz einer anderen Klasse<br />
◮ alle Methoden sind public<br />
◮ es gibt nur einfache Vererbung<br />
◮ jede Berechnung geschieht über das Senden von Nachrichten
Nachrichten in Smalltalk<br />
Es gibt in Smalltalk drei verschiedene syntaktische Formen für<br />
Nachrichten, die an ein Objekt geschickt werden können. Jede<br />
Nachricht besteht aus einem Selektor ( ”<br />
Name“ der Nachricht) <strong>und</strong><br />
eventuellen Argumenten. Der Selektor bestimmt die anzuwendende<br />
Methode.<br />
1. empfänger unäreNachricht<br />
Die Nachricht besteht nur aus einem Selektor.<br />
2. empfänger binäreNachricht<br />
Dabei besteht der Selektor einer binären Nachricht aus einem<br />
oder zwei speziellen Zeichen (wie etwa +, -, *, /, //,
Zusammengesetzte Nachrichten werden in folgender Reihenfolge<br />
abgearbeitet:<br />
1. geklammerte Nachrichten<br />
2. unäre Nachrichten (linksassoziativ!)<br />
3. binäre Nachrichten (linksassoziativ!)<br />
4. Schlüsselwort-Nachrichten
◮ Variablen müssen vor ihrem Gebrauch deklariert werden. In<br />
Smalltalk ist einer Variablen im Gegensatz zu ” üblichen“<br />
<strong>Programmiersprachen</strong> kein Typ zugeordnet!<br />
◮ Das Binden eines Objekts an eine Variable geschieht über das<br />
Wertzuweisungszeichen (:=).<br />
◮ Ein vorangestelltes ˆ-Zeichen wirkt wie eine return-Anweisung<br />
in üblichen <strong>Programmiersprachen</strong>.<br />
◮ In Smalltalk ist durch Konvention festgelegt, dass globale<br />
Variable mit einem Großbuchstaben beginnen müssen.
Steuerstrukturen in Smalltalk<br />
1. Eine Reihe von Smalltalk-Ausdrücken kann mit eventuellen<br />
formalen Parametern zu einem Block zusammengefasst<br />
werden. Solch ein Block bildet eine unbenannte Funktion. Will<br />
man den Block auswerten, so muss man ihm eine<br />
value-Nachricht mit eventuellen Argumenten schicken.<br />
2. Steuerbefehle sind nicht Teil der Sprache Smalltalk, sondern<br />
können von den Benutzern selbst geschrieben werden. Sie<br />
werden meist durch das Versenden von Nachrichten mit<br />
Blockargumenten an boolesche Objekte realisiert.<br />
3. Schleifen (Iterationen) werden über Iteratoren realisiert, die<br />
den Schleifenrumpf als Blockargument übergeben bekommen.