Grundlagen der Informatik I “Programmierung”
Grundlagen der Informatik I “Programmierung” Grundlagen der Informatik I “Programmierung”
leeren Verweis (d.h. von machen Personen sind die Eltern unbekannt), da im Rechner keine unendliche Menge von Objekten verarbeitet werden können. 3.3 Routinen In den bisher betrachteten Klassen haben wir nur Attribute definiert, welche die Struktur der Objekte erklären, die in dieser Klasse zusammengefaßt werden. Die Darstellung der Exemplare eines Typs ist aber nicht die einzige Dienstleistung, die ein abstrakter Datentyp bereitstellt. Neben der Struktur der Exemplare gehören zu einem abstrakten Datentyp auch Operationen auf diesen Exemplaren, die nach außen zur Verfügung gestellt werden. Diese Operationen werden in Programmiersprachen durch sogenannte Routinen beschrieben. Es gibt zwei Arten von Routinen. • Prozeduren können durch Ausführung einer Aktion den Zustand eines Objektes ändern. • Funktionen berechnen Werte, die sich aus dem Zustand der Objekte ergeben. Dabei verstehen wir unter dem Zustand eines Objektes zu einem beliebigen Zeitpunkt der Ausführung eines Softwaresystems die Gesamtheit der Werte seiner Komponenten zu diesem Zeitpunkt. Ein Prozeduraufruf kann also die Werte von einem oder mehreren Komponenten eines Objektes ändern, während ein Funktionsaufruf einen Wert liefert, der aus den Werten der Komponenten eines Objektes berechnet wurde. 3.3.1 Aufruf von Routinen Wir hatten bereits erwähnt, daß es – von außen betrachtet – keinen Unterschied macht, ob ein Wert direkt durch den Zugriff auf eine Komponente oder durch die Berechnung einer Funktion bestimmt wird. Aus diesem Grunde verwendet Eiffel für alle Operationen, seien es nun Zugriffe auf Komponenten, Aufrufe von Funktionen oder Aufrufe von Prozeduren die gleiche Syntax, nämlich eine Punktnotation. Beispiel 3.3.1 Nehmen wir an wir hätten für die Klasse PERSON die folgenden zusätzlichen features definiert: Die Funktion anzahl vornamen soll aus der Komponente vornamen die Anzahl der darin enthaltenen Vornamen bestimmen. Die Funktion alter soll bei Eingabe einer Jahreszahl das Alter der Person berechnen, sofern diese dann noch lebt. Die Prozedur setze todesjahr soll bei Eingabe einer Jahreszahl das Todesjahr einer Person auf das angegebene Jahr setzen. Es bezeichne p ein bereits existierendes Objekt 8 der Klasse PERSON. Dann liefert • p.vornamen die aktuelle Komponente von p, welche dem Attribut vornamen entspricht, • p.anzahl vornamen die aktuelle Anzahl der Vornamen des Objektes p, • p.alter(1993) das berechnete Alter der mit p bezeichneten Person im Jahre 1993, • p.setze todesjahr(1993) eine Veränderung des Zustandes von p: die Komponente, welche dem Attribut todesjahr entspricht wird auf 1993 gesetzt. Man beachte, daß in allen 4 Fällen die gleiche Notation entity.operation(argumente) . Sie bedeutet, daß die angegebene Operation mit den Argumenten auf das durch entity (Größe) bezeichnete Objekt angewandt wird. Es ist egal, ob diese Operation eine Funktion, eine Prozedur, oder etwa nur ein Komponentenzugriff ist – nach außen sieht es immer gleich aus. Dies entspricht einem der Grundgedanken der Datenkapselung: den Benutzer geht es nichts an, wie eine Klasse ihre Dienstleistungen ausführt – nicht einmal, ob dies die Lieferung einer gespeicherten Information oder eine Berechnung ist. 8 Um genau zu sein: wenn eine Variable p ein bereits existierendes Objekt bezeichnet, dann bedeutet das gemäß der in Abschnitt 3.2.3 besprochenen Denkweise natürlich, daß sie ein Verweis auf dieses Objekt ist, wenn der Typ dieser Variablen kein einfacher Datentyp ist. Hierauf kommen wir später noch im Detail zu sprechen.
Entwurfsprinzip 3.3.2 (Punktnotation) Alle Aufrufe von Funktionen, Prozeduren und Komponentenzugriffen auf ein Objekt werden in Eiffel einheitlich in der Notation entity.operation(argumente) ausgedrückt. Eine Operation entity.operation(argumente) greift auf ein Objekt über den Verweis zu, der durch entity bezeichnet wird. Damit dies fehlerfrei geschehen kann, muß das zugehörige Objekt natürlich existieren, d.h. der zu entity gehörende Verweis darf nicht leer sein. Dies gilt für alle Operationen, insbesondere auch für Zugriffe auf Komponenten. Der Versuch, auf ein Merkmal eines leeren Verweises zuzugreifen, ist einer der häufigsten Ursachen für Laufzeitfehler, die bei der Eiffel-Programmierung auftauchen. In “klassischen” Programmiersprachen wie Pascal wird anstelle von entity.operation(argumente) die Notation operation(objekt,argumente) benutzt. Bei einer ersten Betrachtung scheint diese Form symmetrischer zu sein als die von Eiffel. Jedoch benutzt Pascal beim Zugriff auf Komponenten eines Verbundes ebenfalls die Punktnotation und verlangt also vom Benutzer, die Unterscheidung zwischen Zugriff und Funktion vorzunehmen. Eiffel ist da einheitlicher. Der spezielle Grund für die Punktnotation, bei der das Objekt am Anfang jedes Aufrufs genannt wird, ist wiederum die objektorientierte Denkweise: es ist das wichtigste zu sagen, auf welchem Objekt eine Operation ausgeführt wird. Aus diesem Grunde soll das Objekt durch die Notation besonders hervorgehoben werden. 3.3.2 Definition von Routinen Nachdem wir gesehen haben, wie man Routinen aufruft, wollen wir nun zeigen, wie man sie definiert. Dazu muß als wichtigstes herausgehoben werden, daß Routinen prinzipiell nur als Dienstleistungen einer Klasse definiert werden können und nicht etwa als unabhängige “Unterprogramme”. 9 Routinen werden daher gleichberechtigt zu den Attributen einer Klasse als Features aufgeschrieben. Abbildung 3.10 zeigt eine abstrahierte Implementierung der in Beispiel 3.3.1 benutzten Routinen innerhalb der Klasse PERSON. class PERSON feature name, vornamen: STRING; geburtsjahr, todesjahr: INTEGER; nationalität: STRING; vater, mutter: PERSON; anzahl vornamen: INTEGER is -- Anzahl der Vornamen bestimmen do Result := Anzahl der Vornamen in vornamen end; -- anzahl vornamen alter(jahr:INTEGER): INTEGER is -- Alter im gegebenen Jahr bestimmen do Result := jahr - geburtsjahr end; -- alter setze todesjahr(jahr:INTEGER) is -- todesjahr auf jahr setzen do todesjahr := jahr end -- setze todesjahr end -- class PERSON Abbildung 3.10: Klassendefinition mit Routinen Neben den Attributen name, vornamen, geburtsjahr, todesjahr, nationalität, vater und mutter enthält die Klasse PERSON drei Routinen, die man an dem Vorhandensein der Schlüsselwortfolge is...do...end erkennt. Diese Folge begrenzt den Rumpf einer Routine. Eine Routine kann formale Argumente in Klammern 9 Die im Abschnitt 3.3.4 vorgestellten Operationen, die auf allen Klassen Gültigkeit haben, sind daher in Eiffel als Dienstleistungen einer Klasse ANY realisiert, die ihre Konzepte auf alle Klassen vererbt.
- Seite 38 und 39: 1.3.4 Prozeduralisierung Das Beispi
- Seite 40 und 41: Man könnte das Objektkonzept von d
- Seite 43 und 44: Kapitel 2 Logik und formale Sprachb
- Seite 45 und 46: 2.1 Formale Sprachbeschreibungen Di
- Seite 47 und 48: möglichen Alternativen auf der rec
- Seite 49 und 50: Wenn wir eine Menge von Sprachen be
- Seite 51 und 52: Da wir im folgenden den Begriff der
- Seite 53 und 54: Für die Zuordnung zwischen der Obj
- Seite 55 und 56: Diese Form der Definition findet ih
- Seite 57 und 58: K1: Kommutativ-Gesetz E1 ∧E2 ≡
- Seite 59 und 60: Axiomenschemata: L1 A ∨ ¬A L2 (A
- Seite 61 und 62: In dem Kapitel über die Programmve
- Seite 63 und 64: 2. Es werden Quantoren eingeführt,
- Seite 65 und 66: s (T, state) = wahr s (F, state) =
- Seite 67 und 68: • x ist gebunden in p(t1, ..., tn
- Seite 69 und 70: Der Wert einer Aussage mit mehreren
- Seite 71 und 72: Wichtig ist, daß die Auswahl des W
- Seite 73 und 74: Wir geben hier eine Funktion an, di
- Seite 75: wichtigste formale Sprache zur Besc
- Seite 78 und 79: Wir wollen jedoch deutlich darauf h
- Seite 80 und 81: Buch, sondern sollten besser als ei
- Seite 82 und 83: Viel sinnvoller ist es, bei der Bes
- Seite 84 und 85: Entsprechend ihrer beabsichtigten B
- Seite 86 und 87: Klassen sind rein statische Beschre
- Seite 90 und 91: haben wie z.B. “(jahr:INTEGER)”
- Seite 92 und 93: und diese erzeugten Objekte mit Ver
- Seite 94 und 95: Bei der Erzeugung von Objekten mitt
- Seite 96 und 97: über den Namen eines Attributs Wer
- Seite 98 und 99: • Die Veränderung und Auswertung
- Seite 100 und 101: 3.5 Copy- und Referenz-Semantik In
- Seite 102 und 103: 3.5.2 expanded: Klassen mit Copy-Se
- Seite 104 und 105: class LIST[X] creation new feature
- Seite 106 und 107: Dieses Problem wird durch das Konze
- Seite 108 und 109: 3.7.1 Zusicherungen Eiffel logische
- Seite 110 und 111: class ARRAY[X] creation make featur
- Seite 112 und 113: class ARRAY[X] creation make featur
- Seite 114 und 115: formulieren und zu überwachen. Eig
- Seite 116 und 117: 3.8.2 Export geerbter Features Norm
- Seite 118 und 119: Ist also p ein Personenobjekt und a
- Seite 120 und 121: Definition 3.8.5 (Konformität) Ein
- Seite 122 und 123: Um die Beschreibung des Typsystems
- Seite 124 und 125: Nachkommenklassen aber folgt entity
- Seite 126 und 127: deferred class LIST[X] feature leng
- Seite 128 und 129: class STUDENT feature universität:
- Seite 130 und 131: Das Problem ist nun, daß beide Elt
- Seite 132 und 133: Prinzipiell wäre es sogar möglich
- Seite 134 und 135: Das bedeutet also, daß die Erbenkl
- Seite 136 und 137: Die eigentliche Montage eines Syste
leeren Verweis (d.h. von machen Personen sind die Eltern unbekannt), da im Rechner keine unendliche Menge<br />
von Objekten verarbeitet werden können.<br />
3.3 Routinen<br />
In den bisher betrachteten Klassen haben wir nur Attribute definiert, welche die Struktur <strong>der</strong> Objekte erklären,<br />
die in dieser Klasse zusammengefaßt werden. Die Darstellung <strong>der</strong> Exemplare eines Typs ist aber nicht die<br />
einzige Dienstleistung, die ein abstrakter Datentyp bereitstellt. Neben <strong>der</strong> Struktur <strong>der</strong> Exemplare gehören zu<br />
einem abstrakten Datentyp auch Operationen auf diesen Exemplaren, die nach außen zur Verfügung gestellt<br />
werden. Diese Operationen werden in Programmiersprachen durch sogenannte Routinen beschrieben. Es gibt<br />
zwei Arten von Routinen.<br />
• Prozeduren können durch Ausführung einer Aktion den Zustand eines Objektes än<strong>der</strong>n.<br />
• Funktionen berechnen Werte, die sich aus dem Zustand <strong>der</strong> Objekte ergeben.<br />
Dabei verstehen wir unter dem Zustand eines Objektes zu einem beliebigen Zeitpunkt <strong>der</strong> Ausführung eines<br />
Softwaresystems die Gesamtheit <strong>der</strong> Werte seiner Komponenten zu diesem Zeitpunkt. Ein Prozeduraufruf kann<br />
also die Werte von einem o<strong>der</strong> mehreren Komponenten eines Objektes än<strong>der</strong>n, während ein Funktionsaufruf<br />
einen Wert liefert, <strong>der</strong> aus den Werten <strong>der</strong> Komponenten eines Objektes berechnet wurde.<br />
3.3.1 Aufruf von Routinen<br />
Wir hatten bereits erwähnt, daß es – von außen betrachtet – keinen Unterschied macht, ob ein Wert direkt<br />
durch den Zugriff auf eine Komponente o<strong>der</strong> durch die Berechnung einer Funktion bestimmt wird. Aus diesem<br />
Grunde verwendet Eiffel für alle Operationen, seien es nun Zugriffe auf Komponenten, Aufrufe von Funktionen<br />
o<strong>der</strong> Aufrufe von Prozeduren die gleiche Syntax, nämlich eine Punktnotation.<br />
Beispiel 3.3.1 Nehmen wir an wir hätten für die Klasse PERSON die folgenden zusätzlichen features definiert:<br />
Die Funktion anzahl vornamen soll aus <strong>der</strong> Komponente vornamen die Anzahl <strong>der</strong> darin enthaltenen Vornamen<br />
bestimmen. Die Funktion alter soll bei Eingabe einer Jahreszahl das Alter <strong>der</strong> Person berechnen,<br />
sofern diese dann noch lebt. Die Prozedur setze todesjahr soll bei Eingabe einer Jahreszahl das Todesjahr<br />
einer Person auf das angegebene Jahr setzen.<br />
Es bezeichne p ein bereits existierendes Objekt 8 <strong>der</strong> Klasse PERSON. Dann liefert<br />
• p.vornamen die aktuelle Komponente von p, welche dem Attribut vornamen entspricht,<br />
• p.anzahl vornamen die aktuelle Anzahl <strong>der</strong> Vornamen des Objektes p,<br />
• p.alter(1993) das berechnete Alter <strong>der</strong> mit p bezeichneten Person im Jahre 1993,<br />
• p.setze todesjahr(1993) eine Verän<strong>der</strong>ung des Zustandes von p: die Komponente, welche dem Attribut<br />
todesjahr entspricht wird auf 1993 gesetzt.<br />
Man beachte, daß in allen 4 Fällen die gleiche Notation entity.operation(argumente) . Sie bedeutet, daß<br />
die angegebene Operation mit den Argumenten auf das durch entity (Größe) bezeichnete Objekt angewandt<br />
wird. Es ist egal, ob diese Operation eine Funktion, eine Prozedur, o<strong>der</strong> etwa nur ein Komponentenzugriff<br />
ist – nach außen sieht es immer gleich aus. Dies entspricht einem <strong>der</strong> Grundgedanken <strong>der</strong> Datenkapselung:<br />
den Benutzer geht es nichts an, wie eine Klasse ihre Dienstleistungen ausführt – nicht einmal, ob dies die<br />
Lieferung einer gespeicherten Information o<strong>der</strong> eine Berechnung ist.<br />
8 Um genau zu sein: wenn eine Variable p ein bereits existierendes Objekt bezeichnet, dann bedeutet das gemäß <strong>der</strong> in Abschnitt<br />
3.2.3 besprochenen Denkweise natürlich, daß sie ein Verweis auf dieses Objekt ist, wenn <strong>der</strong> Typ dieser Variablen kein einfacher<br />
Datentyp ist. Hierauf kommen wir später noch im Detail zu sprechen.