08.02.2014 Aufrufe

Kapitel 3.3 - Fachgebiet Programmiersprachen und Übersetzer

Kapitel 3.3 - Fachgebiet Programmiersprachen und Übersetzer

Kapitel 3.3 - Fachgebiet Programmiersprachen und Übersetzer

MEHR ANZEIGEN
WENIGER ANZEIGEN

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.

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!