Grundlagen der Informatik I “Programmierung”

Grundlagen der Informatik I “Programmierung” Grundlagen der Informatik I “Programmierung”

22.08.2013 Aufrufe

Prozeduraufruf Effekt putint(n) Ausgabe des Wertes von n auf die Ausgabedatei putreal(r) Ausgabe des Wertes von r auf die Ausgabedatei putchar(c) Ausgabe des Wertes von c auf die Ausgabedatei putstring(s) Ausgabe des Wertes von s auf die Ausgabedatei new line nächste Ausgabe erfolgt am Beginn der nächsten Zeile n muß ein Integerausdruck, r ein Integer- oder Realausdruck, c ein Characterausdruck und s ein Stringausdruck sein. Abbildung 4.13: Die wichtigsten Routinen für die Ausgabe Um also eine Ein- oder Ausgabe ausführen zu können, muß man ein feature vom Typ STANDARD FILES deklarieren und dann über qualifizierte Aufrufe die entsprechenden Routinen anstoßen. Um dies zu vereinfachen, wurde in der Klasse ANY, von der jede Klasse automatisch erbt, ein feature io:STANDARD FILES vordefiniert. Daraus ergibt sich die folgende Schreibweise für Ein- und Ausgaben. io.putint(43); io.new line; io.putstring(" Dies war eine 43"); io.new line; io.putstring("...................") erzeugt als Ausgabe 43 Dies war eine 43 ................... Um eine Integerzahl einzulesen und an eine Variable n zu übergeben, benötigt man folgende Anweisungen: io.readint; n := io.lastint Auf den ersten Blick erscheint diese Form der Ein- und Ausgabe etwas seltsam und umständlich. Der qualifizierte Aufruf mit io wirkt unnötig kompliziert und die Trennung des Einlesens in eine Einleseprozedur mit anschließendem Funktionsaufruf noch viel mehr. Schließlich erscheint es lästig, daß man bei Lese- und Schreiboperationen immer den Typ des Wertes mit angeben muß. Dennoch gibt es für alles gute Gründe. Der qualifizierte Aufruf macht klar, daß es sich bei Ein- und Ausgabe um Dienstleistungen handelt und nicht um Bestandteile der Sprache. Den Klienten dieser Dienstleistung interessiert ja eigentlich nur, daß er bestimmte Werte als Eingaben bekommt bzw. an den Benutzer ausgeben kann. Wie die Ein- und Ausgabe realisiert ist – z.B. ob mit oder ohne Window-Unterstützung – geht ihn nichts an. Der Vorteil dieser Sicht ist, daß die einfachen Dienstleistungen durch verbesserte Versionen ersetzt werden können (mit Maus- und Menüsteuerung oder besserer Fehlerbehandlung), ohne das deshalb das Programm geändert werden muß. 36 Die etwas umständlich wirkende Eingabe ergibt sich aus der strikte Trennung von Prozeduren und Funktionen. Einlesen und einen Eingabewert zu bestimmen sind, genau besehen, zwei Operationen. Die erste nimmt eine Veränderung des Objektes input vor (der Lesekopf des Eingabebandes wird verschoben und ein Pufferobjekt beschrieben) die zweite bestimmt einen Wert, der zugewiesen werden kann. Es wäre nicht sinnvoll, ausgerechnet bei Ein- und Ausgabe von der Eiffel-Philosophie Abstand zu nehmen und die Eingabe als Funktion mit Seiteneffekten oder als Prozedur mit einer Veränderung übergebener Parameter zu realisieren. Die Unterscheidung des Typs bei Lese- und Schreibroutinen entspricht dem strengen Typkonzept von Eiffel, von dem man auch bei Ein- und Ausgabe nicht so einfach abweichen sollte. Der Vorteil dieser Vorgehensweise ist, daß fehlerhafte Eingaben und Ausgaben dort entdeckt und behandelt werden können, wo sie entstehen (nämlich bei Ein- und Ausgabe) und nicht erst nachträglich bei der Verarbeitung zu Laufzeitfehlern führen. Nichtsdestotrotz ist die Eiffel-Schreibweise gewöhnungsbedürftig, vor allem, wenn man bereits mit anderen Programmiersprachen gearbeitet hat, in denen allgemeinere Lese- und Schreibroutinen erlauben, gleichzeitig mehrere Werte verschiedenen Typs einzulesen bzw. in einen definierbaren Muster auszugeben. Das Fehlen derartiger Routinen macht die Programmierung einfacher Ein- und Ausgabe in Eiffel zu einer aufwendigen Tätigkeit. Erst bei der Gestaltung komplexer Benutzeroberflächen kann Eiffel seine Vorteile wieder ausspielen. 36 Wer dennoch den qualifizierten Aufruf umgehen will, der kann die Klasse STANDARD FILES in der Erbklausel aufführen.

4.3.10 Die Verifikation rekursiver Routinen In den bisherigen Abschnitten haben wir alle Arten von Anweisungen besprochen und Regeln aufgestellt, wie ihre Korrektheit zu beweisen ist. Bei diesen Betrachtungen haben wir jedoch einen Sonderfall außer Acht gelassen, der bei der Verifikation noch Schwierigkeiten erzeugen kann, nämlich Routinen die sich selbst direkt oder indirekt wieder aufrufen. Unser Programmstück zur Berechnung der Fakultät (siehe Beispiel 4.3.9 auf Seite 161) hätten wir zum Beispiel auch durch folgende rekursive Funktion ausdrücken können. fak berechnung(n:INTEGER):INTEGER is require n>0 do if n=1 then Result := 1 else Result := n * fak berechnung(n-1) end end Dies spiegelt einen Gedankengang der Mathematik wieder, die Fakultätsfunktion statt durch eine Interation durch zwei Gleichungen 1!=1 ∧ n!=n*(n-1)! auszudrücken, und ist um einiges eleganter (und leichter zu verifizieren) als die Schleife. Dennoch tauchen rekursive Routinen tauchen in der Praxis recht selten auf, da der gleiche Effekt mit Schleifen oft effizienter zu erreichen ist. In manchen Fällen ist dies allerdings recht schwierig und deshalb kann man auf die Möglichkeit rekursiver Routinenaufrufe nicht sinnvoll verzichten. So gibt es zum Beispiel Sortierprogramme für Felder, die zunächst einen “Mittelwert” bestimmen, das Feld in zwei Teile zerlegen, die nur größere bzw. nur kleinere Werte enthalten, und dann in jeden Teil wieder nach dem gleichen Verfahren sortieren, bis es nichts mehr zu teilen gibt. Eine rekursive Beschreibung dieses sehr effizienten Verfahrens (“Quicksort”) ist auf Grundlage dieser Beschreibung sehr leicht zu geben. Ein Programm, welches dasselbe nur durch Einsatz von Schleifen erreicht, ist dagegen nicht so leicht zu finden. Darüber hinaus gibt es arithmetische Funktionen, die nur durch rekursive Gleichungen spezifiziert werden können. Ein beliebtes Beispiel hierfür sind die Fibonaccizahlen, die wie folgt beschrieben sind. Die erste Fibonaccizahl ist 1, die zweite ebenfalls. Alle größeren Fibonaccizahlen ergeben sich aus der Summe ihrer beiden Vorgänger. Bezeichnet man die n-te Fibonaccizahl mit fib(n), so kann man dies durch zwei Gleichungen ausdrücken: fib(1)=fib(2)=1, fib(n+2)=fib(n+1)+fib(n). Ein rekursives Programm für die Fibonaccizahlen ist leicht zu schreiben. Wir müssen nur jede Eingabe einheitlich durch den Parameter n beschreiben, die Fälle n=1, n=2 und n>2 unterscheiden und die zweite Gleichung durch eine Indextransformation n → n-2 umschreiben. fib(n:INTEGER):INTEGER is require n>0 do if n

Prozeduraufruf Effekt<br />

putint(n) Ausgabe des Wertes von n auf die Ausgabedatei<br />

putreal(r) Ausgabe des Wertes von r auf die Ausgabedatei<br />

putchar(c) Ausgabe des Wertes von c auf die Ausgabedatei<br />

putstring(s) Ausgabe des Wertes von s auf die Ausgabedatei<br />

new line nächste Ausgabe erfolgt am Beginn <strong>der</strong> nächsten Zeile<br />

n muß ein Integerausdruck, r ein Integer- o<strong>der</strong> Realausdruck,<br />

c ein Characterausdruck und s ein Stringausdruck sein.<br />

Abbildung 4.13: Die wichtigsten Routinen für die Ausgabe<br />

Um also eine Ein- o<strong>der</strong> Ausgabe ausführen zu können, muß man ein feature vom Typ STANDARD FILES deklarieren<br />

und dann über qualifizierte Aufrufe die entsprechenden Routinen anstoßen. Um dies zu vereinfachen,<br />

wurde in <strong>der</strong> Klasse ANY, von <strong>der</strong> jede Klasse automatisch erbt, ein feature io:STANDARD FILES vordefiniert.<br />

Daraus ergibt sich die folgende Schreibweise für Ein- und Ausgaben.<br />

io.putint(43);<br />

io.new line;<br />

io.putstring(" Dies war eine 43");<br />

io.new line;<br />

io.putstring("...................")<br />

erzeugt als Ausgabe<br />

43<br />

Dies war eine 43<br />

...................<br />

Um eine Integerzahl einzulesen und an eine Variable n zu übergeben, benötigt man folgende Anweisungen:<br />

io.readint; n := io.lastint<br />

Auf den ersten Blick erscheint diese Form <strong>der</strong> Ein- und Ausgabe etwas seltsam und umständlich. Der qualifizierte<br />

Aufruf mit io wirkt unnötig kompliziert und die Trennung des Einlesens in eine Einleseprozedur<br />

mit anschließendem Funktionsaufruf noch viel mehr. Schließlich erscheint es lästig, daß man bei Lese- und<br />

Schreiboperationen immer den Typ des Wertes mit angeben muß. Dennoch gibt es für alles gute Gründe.<br />

Der qualifizierte Aufruf macht klar, daß es sich bei Ein- und Ausgabe um Dienstleistungen handelt und<br />

nicht um Bestandteile <strong>der</strong> Sprache. Den Klienten dieser Dienstleistung interessiert ja eigentlich nur, daß er<br />

bestimmte Werte als Eingaben bekommt bzw. an den Benutzer ausgeben kann. Wie die Ein- und Ausgabe<br />

realisiert ist – z.B. ob mit o<strong>der</strong> ohne Window-Unterstützung – geht ihn nichts an. Der Vorteil dieser Sicht<br />

ist, daß die einfachen Dienstleistungen durch verbesserte Versionen ersetzt werden können (mit Maus- und<br />

Menüsteuerung o<strong>der</strong> besserer Fehlerbehandlung), ohne das deshalb das Programm geän<strong>der</strong>t werden muß. 36<br />

Die etwas umständlich wirkende Eingabe ergibt sich aus <strong>der</strong> strikte Trennung von Prozeduren und Funktionen.<br />

Einlesen und einen Eingabewert zu bestimmen sind, genau besehen, zwei Operationen. Die erste nimmt eine<br />

Verän<strong>der</strong>ung des Objektes input vor (<strong>der</strong> Lesekopf des Eingabebandes wird verschoben und ein Pufferobjekt<br />

beschrieben) die zweite bestimmt einen Wert, <strong>der</strong> zugewiesen werden kann. Es wäre nicht sinnvoll, ausgerechnet<br />

bei Ein- und Ausgabe von <strong>der</strong> Eiffel-Philosophie Abstand zu nehmen und die Eingabe als Funktion mit<br />

Seiteneffekten o<strong>der</strong> als Prozedur mit einer Verän<strong>der</strong>ung übergebener Parameter zu realisieren.<br />

Die Unterscheidung des Typs bei Lese- und Schreibroutinen entspricht dem strengen Typkonzept von Eiffel,<br />

von dem man auch bei Ein- und Ausgabe nicht so einfach abweichen sollte. Der Vorteil dieser Vorgehensweise<br />

ist, daß fehlerhafte Eingaben und Ausgaben dort entdeckt und behandelt werden können, wo sie entstehen<br />

(nämlich bei Ein- und Ausgabe) und nicht erst nachträglich bei <strong>der</strong> Verarbeitung zu Laufzeitfehlern führen.<br />

Nichtsdestotrotz ist die Eiffel-Schreibweise gewöhnungsbedürftig, vor allem, wenn man bereits mit an<strong>der</strong>en<br />

Programmiersprachen gearbeitet hat, in denen allgemeinere Lese- und Schreibroutinen erlauben, gleichzeitig<br />

mehrere Werte verschiedenen Typs einzulesen bzw. in einen definierbaren Muster auszugeben. Das Fehlen<br />

<strong>der</strong>artiger Routinen macht die Programmierung einfacher Ein- und Ausgabe in Eiffel zu einer aufwendigen<br />

Tätigkeit. Erst bei <strong>der</strong> Gestaltung komplexer Benutzeroberflächen kann Eiffel seine Vorteile wie<strong>der</strong> ausspielen.<br />

36 Wer dennoch den qualifizierten Aufruf umgehen will, <strong>der</strong> kann die Klasse STANDARD FILES in <strong>der</strong> Erbklausel aufführen.

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

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!