Blöcke, Ausnahmen, Überladen

Block:  Ein Block ist eine sich geschlossene, durch begin/end geklammerte Folge von Anweisungen mit Deklarations- bzw. Ausnahmeteil am Anfang bzw. Ende.
Die Bezeichnungen des Deklarationsteils können nur innerhalb dieses Blocks und seiner Unterblöcke verwendet werden. Nach Verlassen des Blocks sind die Bezeichner und die entsprechenden Objekte undefiniert/unbekannt.
Ein Bezeichner, der explizit im Deklarationsteil eines Blocks oder implizit als Laufvariable/Marke/Bezeichnung eingeführt wird, heißt lokal zu diesem Block. Bezeichner, die in Oberblöcken deklariert wurden, heißen global in den Unterblöcken.
Ein in einem Oberblock deklarierter Bezeichner wird durch Neudeklaration in einem Unterblock „ausgeblendet“ und kann nicht mehr angesprochen werden.

Bezeichner:  Ein Bezeichner \(X\) bezieht sich stets auf die Deklaration von \(X\), die sich im Deklarationsteil des innersten Blocks befindet.

Lebensdauer/Sichtbarkeit:  Die Lebensdauer eines Bezeichners (und des zugehörigen Objekts) ist der Block, in dem der Bezeichner deklariert wurde. Der Bezeichner/das Objekt lebt genau ab der Stelle seiner Deklaration, bis zu der Stelle, an dem der Block verlassen wird. Wird der Block später neu betreten, so wird ein neues Objekt erzeugt.
Der Gültigkeits-/Sichtbarkeitsbereich eines Bezeichners/Objekts ist der Teil der Lebensdauer, in dem unmittelbar über den Bezeichner auf das Objekt zugegriffen werden kann. Ein Objekt kann unsichtbar und dann wieder sichtbar werden.

Speicher:  Blöcke und die Variablen werden auf dem Stack gespeichert und verwaltet.

Vorteile von Blöcken:  getrennte Entwicklung/Optimierung, besseres Verständnis;
Hilfsvariablen und Zwischenrechnung verschwinden nach Abarbeiten;
Vermeidung von Namenskonflikten bei größeren Programmeinheiten;
Einfluss auf den Stack und eigene gezielte Verwaltung des Speicherplatzes.

Überladen:  Überladen ist die Mehrfachdeklaration eines Bezeichners (d. h. einem Bezeichner sind mehrere verschiedene Objekte zugeordnet). An jeder Stelle des Programms muss aber aus dem Kontext eindeutig hervorgehen, welche Bedeutung gemeint ist.
In Ada ist Überladen für bestimmte Bezeichner (Literale in Aufzählungstypen, Funktionen, Operatoren, Unterprogramme) zulässig, andere (Datenobjekte, implizite Bezeichner) dürfen nicht überladen werden.
Bei Unterprogrammen ist Überladen zulässig, falls sich die Deklarationen in der Reihenfolge der Parametertypen, in mindestens einem Parametertyp oder im Ergebnistyp unterscheiden.

Ausnahmen:  In Ada kann am Ende jedes Blocks eine Ausnahmebehandlung definiert werden. Dazu deklariert man im Deklarationsteil (z. B. des Packages) mittels
Fehler : exception; die Ausnahme. In einem Unterprogramm wird diese mittels
raise Fehler; geworfen. Fehler kann man in einem übergeordneten Unterprogramm durch exception when Fehler => Put ("1"); when others => Put ("2");
kontrolliert abfangen. Im Falle eines Fehlers wird dabei nach der Ausnahmebehandlung gesucht (notfalls wird zum übergeordneten Block gewechselt). Dabei werden u. U. auch Prozeduren/ Funktionen verlassen und der entsprechende Platz auf dem Stack freigegeben.
In Ada sind vier Standardfehler Constraint_Error, Program_Error, Strorage_Error, Tasking_Error vorhanden.

Prozeduren und Funktionen

Unterprogramm:  Eine Folge von Deklarationen und Anweisungen kann zur einer Programmeinheit (Prozedur/Unterprogramm) mit Namen und formalen Parametern zusammengefasst werden. Eine Prozedur besteht aus Spezifikation (Name, Parameter) und Rumpf (Deklarationsteil, Anweisungen). Seiteneffekte, die durch Verwendung globaler Variablen auftreten können, sind zu vermeiden.

Rekursion:  Rekursion ist die Verwendung eines Unterprogramms in seinem eigenen Rumpf.

Parameterübergabe (Pseudocode): 

  • Call-By-Value: Die mit value versehenen formalen Parameter werden als lokale Variablen aufgefasst, denen beim Funktionsaufruf die Werte der aktuellen Parameter zugewiesen werden. Sie dürfen neue Werte erhalten, diese werden jedoch am Ende der Prozedur nicht wieder zurückgeschrieben.

  • Call-By-Reference: Die mit access versehenen formalen Parameter sind Zeiger auf die aktuellen Parameter.

  • Call-By-Name: Die mit name versehenen formalen Parameter werden beim Funktionsaufruf textuell durch die aktuellen Parameter ersetzt.

Kopierregel: 

\(\mathtt {\mathbf {declare}\; X_1 : Typ_1;\; \ldots ;\; X_n : Typ_n;}\)
\(\mathtt {\mathbf {begin}}\)
\(\mathtt {X_1 := \alpha _1;\;\; \ldots ;\;\; X_n := \alpha _n;}\)
\(\mathtt {modifizierterPRUMPF}\)
\(\mathtt {\mathbf {end};}\)

Gegeben sei eine Prozedur

\(\mathtt {\mathbf {procedure}\; }\) \(\mathtt {P(X_1: p"u_1\; T_1;\; \ldots ;\; X_n: p"u_n\; T_n)}\)

\(\mathtt {\mathbf {is}\; PRUMPF;}\), wobei \(\mathtt {p"u_i} \in \{\mathtt {\mathbf {value}}, \mathtt {\mathbf {access}}, \mathtt {\mathbf {name}}\}\) die Parameterübergabe angibt. Der Prozeduraufruf \(\mathtt {P(\alpha _1, \ldots , \alpha _n)}\) mit den aktuellen Parametern

\(\mathtt {\alpha _1}, \mathtt {\ldots }, \mathtt {\alpha _n}\) wird dann durch nebenstehenden Block ersetzt.

Dabei sei \(\mathtt {Typ_i = T_i}\) für \(\mathtt {p"u_i = \mathbf {value}}\), \(\mathtt {Typ_i = \mathbf {access}\; T_i}\) für \(\mathtt {p"u_i = \mathbf {access}}\) und
\(\mathtt {X_i : Typ_i;}\) sowie \(\mathtt {X_i := \alpha _i;}\) entfallen für \(\mathtt {p"u_i = \mathbf {name}}\).
\(\mathtt {modifizierterPRUMPF}\) ist ein Block, der folgendermaßen aus \(\mathtt {PRUMPF}\) entsteht:

  • Jeder formale Parameter \(\mathtt {X_i}\) mit \(\mathtt {p"u_i = \mathbf {access}}\) wird durch \(\mathtt {\mathbf {deref}\; X_i}\) ersetzt.

  • Jeder formale Parameter und jeder lokale Name in \(\mathtt {PRUMPF}\), der gleich einem Namen ist, der in irgendeinem aktuellen Parameter \(\mathtt {\alpha _i}\) mit \(\mathtt {p"u_i = \mathbf {name}}\) vorkommt, wird durchgehend mit einem neuen Namen bezeichnet.

  • Alle \(\mathtt {X_i}\) mit \(\mathtt {p"u_i = \mathbf {name}}\) werden durch \(\mathtt {\alpha _i}\) textuell ersetzt.

  • (Globale Variablen dürfen nicht „lokaler“ werden.)

Dann wird dieser Block ausgeführt. Nach der Ausführung wird er wieder durch den Prozeduraufruf \(P(\alpha _1, \ldots , \alpha _n)\) ersetzt und das Programm setzt mit der folgenden Anweisung fort.
Die obige Kopie des Prozedurrumpfs heißt Inkarnation/konkrete Ausprägung der Prozedur.

Nur Call-By-Value:  Manche Sprachen erlauben nur Call-By-Value als Übergabeart (z. B. C). Jedoch kann man dann einen Pointer als Parameter übergeben, sodass man die referenzierte Variable abändern kann.

Parameterübergabe in Ada:  In Ada gibt es Parameter vom Typ in (formaler Parameter wird wie eine Konstante behandelt, darf nicht verändert werden), out (wird wie eine Variable behandelt, aktueller Parameter muss eine Variable sein, zugewiesene Werte werden erst bei Beendigung der Prozedur dem aktuellen Parameter zugewiesen) und in out (wie out, aber dem formalen Parameter wird wie bei in anfangs der Wert des aktuellen Parameters zugewiesen). In Funktionen sind nur in-Parameter erlaubt (Standard, wenn nicht angegeben).

Moduln

Eigenschaften von Moduln:  in sich abgeschlossene Einheit mit klar definierter Aufgabe; genau definierte Schnittstelle nach außen (nur die dort genannten Eigenschaften sind nach außen hin sichtbar); die interne Arbeitsweise/Implementation ist außen nicht bekannt (zwei Sichten: Außenansicht und Innensicht, die nach außen hin versteckt bleibt); überschaubar, gut zu testen, einfach zu warten; in Bibliotheken aufbewahrbar und leichte Einbaubarkeit in beliebige Programmsysteme

Schematischer Auf bau eines Moduls: 

module <Name des Moduls> is
[with ...; use ...] -- welche anderen Einheiten verwendet werden und in welcher Weise
specification ... -- nach aussen sichtbare Datentypen, Konstanten, Variablen und
               -- "Methoden" (also Funktionen, Operatoren usw.)
[implementation ...] -- weitere (nach aussen nicht sichtbare) Deklarationen sowie
               -- Programme zur Implementierung der Methoden und Typen
[begin ... end] -- Initialisierung, einschliesslich Ausnahmebehandlungen
end module [<Name des Moduls>]

Moduln sind die programmiersprachliche Realisierung von Datentypen. Beispielsweise kann ein „Stack für Zeichen“ als Modul umgesetzt werden.

Moduln in Ada („Pakete“):  Das Schlüsselwort in Ada lautet package, man spricht von Paketen. Spezifikations-/Implementierungsteil werden voneinander getrennt und lauten
package is end ; bzw.
package body is end ;.
Der Implementierungsteil kann entfallen, falls die Spezifikation nur aus Datentyp- und Konstantendeklarationen besteht.
Die Deklaration privater Typen erfolgt durch type is private;, die Struktur des Typs wird am Ende der Spezifikation nach dem Schlüsselwort private angegeben und so vor dem Benutzer versteckt. In Ada sind mit einem Datentype (auch privat) stets die Operationen =, /= und := verbunden. Sollen diese Operationen nicht für die Benutzer des Moduls zugelassen werden, so muss man den Typ als limited private deklarieren.

Polymorphie

Allgemein:  Polymorphie (griechisch: Vielgestaltigkeit) ist ein Grundprinzip der Informatik, das sich durch folgende Maßnahmen äußert: Möglichst lange den konkreten Datentyp von Variablen offen lassen (z. B. unspezifizierte Feldgrenzen), möglichst lange konkrete Realisierung offen lassen (z. B. Spezifikations-/Implementierungsteil trennen) und Parametrisierung von Paketen und Unterprogrammen für den Einsatz in möglichst vielen Programmen (z. B. Generizität).

in der Programmierung:  In der Programmierung spricht man von Polymorphie, falls Bezeichner mehrfach verwendet werden (Überladen), falls Variablen je nach aktueller Umgebung Elemente verschiedener Datentypen bezeichnen, falls Parametrisierung mit Typen erfolgt (also falls Typen als Parameter für Prozeduren/Typen verwendet werden) und falls Generizität bei Unterprogrammen/Moduln verwendet wird.

Generizität: 

-- Spezifikation              -- Implementierung
generic type Element is private; procedure Tausch (A, B : in out Element) is ...
procedure Tausch (A, B : in out Element); begin ... end Tausch;

-- Verwendung
procedure IntTausch is new Tausch (Integer);
X, Y : Integer;
...
IntTausch (X, Y);

In Ada wird der variabel gehaltene Bereich mit generic eingeleitet. Im Beispiel ist Element ein formaler Parameter, der bei der Instanziierung durch is new textuell durch den aktuellen Parameter (hier Integer) ersetzt wird. Ein generic-Parameter darf in Ada nicht bereits im generic-Bereich verwendet werden. Das Problem wird durch generische Pakete gelöst.

Vererbung

Ableitungen von Datentypen:  Ist ein Datentyp bereits deklariert, so kann man durch Hinzufügen weiterer Komponenten aus ihm weitere Datentypen ableiten (Spezialisierung).
Liegen mehrere Datentypen vor, die gewisse Komponenten gemeinsam haben, so kann man diese Gemeinsamkeiten als eigenen Datentyp herausziehen (Generalisierung).

Spezialisierung in Ada:  Mittels type abc is tagged record ... end record;
kann man einen Record erstellen, der erweitert werden soll. Bei der Erweiterung mit einem Unterdatentyp muss der Obertyp per
type xyz is new abc with record ... end record; angegeben werden.
Man spricht beim Vorgang, Eigenschaften an andere Einheiten weiterzureichen, von Vererbung. Die Obertypen heißen Eltern, die Untertypen Kinder. Man sagt, xyz ist ein aus abc abgeleiteter Typ. (Die Eigenschaft tagged vererbt sich automatisch an die Untertypen, d. h. diese können wiederum ohne Zusätze weiter abgeleitet werden.)

Generalisierung in Ada:  Gemeinsame Komponenten kann man in einen Obertyp herausziehen. Man deklariert einen solchen Obertyp mit
type abc is abstract tagged record ... end record;, Untertypen lassen sich dann wie oben erstellen. Der Unterschied ist, dass sich abstrakte Datentypen (wie hier abc) im Gegensatz zu den Untertypen nicht als Variable oder formaler Parameter deklarieren lassen.

Umdefinitionen:  Bei der Vererbung von Typen kann man vererbte Komponenten neu definieren. Die vererbten Komponenten sind dann wegen der Sichtbarkeitsregel automatisch ausgeblendet (overridden).

Mehrfachvererbung:  Es gibt Sprachen (wie Ada), in denen ein Datentyp höchsten einen direkten Obertyp besitzen kann (Einfach-Vererbung). Können Eigenschaften mehrerer Obertypen an einen Datentyp weitergereicht werden, spricht man von Mehrfach-Vererbung.

Objekte

Objekte:  Objekte sind in sich geschlossene Einheiten, die wie Moduln aufgebaut sind. Es gibt ein Schema (Klasse), das aus Attributen und Methoden besteht. Aus diesem kann ein konkretes Objekt (eine Instanz) erzeugt werden. Objekte können einen individuellen Zustand besitzen und miteinander kommunizieren. Durch Vererbung können sie ihre Eigenschaften an neue Objekte/Klassen weiterreichen.

Prinzipien der Objektorientierung:  es gibt nur Objekte (eindeutig über Namen identifizierbar); handeln in eigener Verantwortung; Klassen werden in Bibliotheken aufbewahrt und stehen allen Programmen zur Verfügung…