-- One person's constant is another person's variable --
Kapitel 1: Programmierung
Wie ist ein Ada-Programm aufgebaut? Wir erklären
das an Hand einem Beispiel:
-- Demo für die Ausgabe von Text
-- Eine Einfuehrung in die Programmierung
-- Stand 23.10.2002, Volker Ziesing
with Ada.Text_IO; use Ada.Text_IO;
-- hier beginnt der Ausführungsteil:
procedure Hello is
Zahl: Integer;
begin
Get(Zahl);
Put ("Hello WORLD!");
New_Line;
end Hello;
|
Kopfkommentare: Dokumentieren den Name des Programms und
die weiteren Angaben über
Zweck, Kontext, Verfasser und Änderungsdatum.
Kontextklauseln: mit with wird Paket Ada.Text_IO aus der Bibliothek importiert "use Ada.Text_IO" veranlasst
importierte Objekte mit ihrem Namen direkt
zu verwenden.
Prozedur - Vereinbarung: Prozedurname "Hello", die Parametereingabe
ist hier leer.
lokales Hilfsobjekt: Das Hilfsobjekt "Zahl" ist ein
Bezeichner und bezeichnet hier eine Variable die beliebige Werte aus dem Datentyp Integer aufnimmt.
Eingaberoutine: Get(n) ist eine Eingaberoutine. Sie liest
die Ganzzahl n ein.
Ausgaberoutine: Put(s) ist eine Ausgaberoutine welche eine
Zeichenkette s ausgibt.
|
1.1 Kontrollstukturen in Ada95:
| FOR |
WHILE |
IF |
CASE |
for i in 1..10 loop
Put(i);
end loop; |
while x > 10 loop
x := x + 1;
end loop; |
if a > 0 then return a/b;
elsif a = 0 then Put("div. by 0");
else Put("negative");
end if; |
case c is
when 'a' => a:=a+1;
when 'b' => b:=b+1;
when other => null;
end case; |
Erklärungen zu Schleifen
In Schleifen kann ein Inkrement oder ein Dekrement angegeben werden.
| WHILE: |
Die allgemeine Form der Schleife mit vorheriger Prüfung. Sie wird auch "ablehnende Schleife" genannt, weil eine Anweisung erst
ausgeführt wird, wenn die Schleifenbedingung
erfüllt ist. Sie wird benutzt, wenn die Anzahl der Iterationen vorher unbekannt ist. |
| FOR: |
Eine spezielle Form der WHILE-Schleife. Man nennt sie auch "Laufschleife" oder "Zählschleife". Sie hat einen Start- und einen Endwert
(im obigen Beispiel 1 und 10). Liegt der
Startwert in der Fortschaltungsrichtung bereits
jenseits des Endwertes, so wird der Rumpf
gar nicht ausgeführt ->abweisende Schleife. Wir verwenden FOR immer dann, wenn die
Zahl der Iterationen schon vor dem Eintritt
in die Schleife bekannt ist -> der Bereich
muss vorher lückenlos definiert sein und einen skalaren, diskreten Typ durchlaufen. Hinweis: Die FOR-Schleife kann man auch rückwarts laufen lassen (counting down): for i in reverse 1.. 10 loop Put(i); end
loop; |
| REPEAT: |
Eine Schleife mit nachfolgender Prüfung, deshalb wird sie mindestens einmal durchlaufen.
Zur ihr sagt man auch gerne "annehmende Schleife". REPEAT sollte nur in Betracht gezogen
werden, wenn die zu prüfende
Bedingung erst
in der Schleife entsteht. |
| LOOP: |
Eine Schleife ohne Prüfung. Sie wird erst verlassen wenn die darin
enthaltene EXIT-Anweisung erreicht wird.
Sie kann in Ausnahmefällen auch mit einem
RETURN verlassen werden:
Beispiel: elsif p.wert /= q.wert then return false; |
1.2 Prozeduren in Ada95
| Prozedur-Vereinbarung: |
ein Block, der den Prozedurrumpf umgreift.
Seine Parameter stellen neue gebundene Bezeichner. |
| Prozedurkopf: |
procedure <Prozedurname> <Parametereingabe>
is enthält die Beschreibung der Parameter. |
| Prozedurrumpf: |
ein Block, Gültigkeitsbereich für seine Bezeichner
und Anweisungsfolge mit lokalen Objekte. |
Achtung: Ein mittels in übergebener Parameterwert wird im Rumpf
der Prozedur als Konstante behandelt und
kann nicht , wie in manchen anderen Programmiersprachen,
wie eine lokale Hilfsvariable verwendet werden!
Die Parametereingabe stärker durchleuchtet:

| Parametermodi |
In Ada95 gibt es 3 Parametermodi: |
in: |
Reiner Eingabeparameter; sein Wert darf im Rumpf nicht verändert
werden -> Konstante im Rumpf. Funktionen
haben nur in-Parameter.
|
| out: |
Reiner Ausgabeparameter; sein Wert sollte im Rumpf gesetzt werden;
zu Beginn des Unterprogramms ist sein Wert
undefiniert. -> Wertparameter.
|
| in out: |
Ein- und Ausgabeparameter; sein Wert darf gelesen und verändert werden.
Zu Beginn des Unterprogramms wird der Wert
des aktuellen Parameters in eine lokale Variable
für den formalen Parameter kopiert. Nach
dem Ende des Unterporgramms wird der Wert
der lokalen Variable dem aktuellen Parameter
zugewiesen.-> Durchgangsparameter.
|
procedure summe (summand_a, summand_b:
in
Integer; ergebnis: out Integer)
procedure vertausche (a,b : in out
Integer)
In Ada gibt es die Möglichkeit, die
Namen der Parameter zu verwenden:
function quotient (divident,divisor : integer)
return float is...
Aufruf:
ergebnis := quotient(divident=>10,divisor=>5);
Bemerkungen:
Weil eine Prozedur keine Ergebnisse liefern
kann unterscheidet sie sich von einer Funktion.
Folglich liegt der Sinn einer Prozedur nur
in einer Nebenwirkung (z.B. der Ausgabe von Meldungen).
1.3 Ein Vergleich zwischen den flachen Datenstrukturen
Array und Liste
| Array |
Liste |
+ direktes Springen zwischen den Elementen
möglich
+ die Länge der Folge ist bekannt
(Ausnahme:
dynamische Arrays)
+ bietet sich an für einzelne Stapel
(Array fester Länge -> Stapelhöhe kontrollieren)
+ Teile und Herrsche -> Quicksort
anwendbar
+ geringer Aufwand für Speicherbandbreite
+ direkter Zugriff über die Position
- nicht geeignet für die lineare
Suche
|
+ Mehrfachverkettung (auch rückwärts) möglich
+ Anzahl der Elemente nur durch
Speicherplatz
begrenzt
+ kann dynamisch erweitert bzw.
verringert
werden
- Springen nur mit angelegten
Hash möglich
- nach jeden Durchlauf an den
Anfang zurück
- Navigationspointer nötig
- Elemente schwer durchzählbar
- relativ langsam wegen hohem
Verwaltungsaufwand
- kein binäres Suchen möglich
- Verschieben von mehreren Elementen nur
umständlich realisierbar
|
Das Auslesen der Liste erfordert eventuell
eine (teure) Kopie. Bedienen wir uns hingegen
einer Halde, so dürfen die Elemente in mehreren Listen
vorkommen und ein billiges Umhängen der Listenelemente ist möglich.
Löschen in einer Liste:
Das Listenelement x soll gelöscht werden.
Es gilt ferner: x ist über einen Nebeneinstieg
(Cursor,Hilfszeiger) erreichbar.
| x hat einen Nachfolger y |
x hat keinen Nachfolger |
1. Vertausche x mit y
2. entferne (neuen) Nachfolger y
|
1. Vorgänger vom Listenkopf her suchen
(linearer Suchaufwand)
2. Nachfolger des gefunden Vorgängers aushängen
(das betrifft unser gewünschtes x)
|
Zeigerdefinition in Ada: type List_Element_Zeiger is access List_Element;
1.4 Datenstrukturen in der Programmierung
Wichtige Merkmale:
- In Ada gibt es keine automatische Typanpassung
- Überladung: Ergebnis der Operationen hängt vom Typ der
Operanden ab.
Beispiel: Wir bauen mithilfe eines Pseduocodes die
Typen der Datenstruktur "Schiffsdeck"
auf:
| type |
Deck |
= |
Array [1..10] of Gänge |
|
Gänge |
= |
Array [1..3] of Kabinen |
|
Kabinen |
= |
Array [1..7] of Plätze |
|
Plätze |
= |
Arrary [1..2] of Boolean |
Eine Typdefinition sieht in Ada in Wirklichkeit
so aus:
type <name> is array <Indexbereich>
of <Datentyp>; --Allgemeiner Arraytyp
type wochentag is (MO,DI,MI,DO,FR,SA,SO); -- Aufzählung
type sortierfeld is array (wochentag) of
index; -- Zugriff
n-dimensionales Feld:
type <name> is array (1..a, 1..b, ..
, 1..x) of float;
es stehen n-Bereiche (n Dimensionen in der
Array-Deklaration
Die Zahlendarstellung:
Die Menge der natürlichen Zahlen als Untertyp:
subtype Natural is Integer range 0..Integer'Last;
| floating point: |
Gleitpunkt-/Gleitkomma-Darstellung |
| fixed point: |
Festkomma-Darstellung |
| delta: |
minimal erforderliche Genauigkeit |
| digits: |
Zahl der Ziffern |
1.5 Hinweise zum Programmierstiel
Programme sollten mit kleinstmöglichem Aufwand
korrigier- und modifizierbar sein.
Deshalb sollte man versuchen eine hohe Lokalität druch enge Gültigkeitsbereiche zu erreichen:
- Die auftretenden Programmeinheiten (Prozeduren, Funktionen) sind überschaubar.
- Jeder Bezeichner hat nur eine einzige bestimmte Bedeutung.
- auf den bedingten Sprung verzichten (bekannt
als GOTO) und stattdessen den kontrollierten
Sprung
- Die Kommunikation findet über möglichst wenige
Parameter statt
Faustregeln zu den Kosten vordefinierter
Funktionen:
- Speziellere Funktione sind billiger als generelle
- Absicherungen kosten
- Mehr Funktionalität kostet
- Zahl der Grundoperationen kann man gut schätzen
(anhand typischer Impelementierungen)
- Konkrete, maschinenabhänge Kosten einer Operation
kaum.
1.6 Besonderheiten in Ada 95
- Generische Pakete (Schlüsselwörter generic und package)
- beschränkte und private Typen (mit dem
Schlüsselwort private)
spezielle Anweisungen:
| exit: |
beendet Schleifen |
| return: |
explizite Rückkehr aus Einheiten |
| raise: |
aktivieren einer Ausnahmebehandlung |
| delay: |
- |
| code: |
- |
| abort: |
- |
Überraschungen bei Pointer-Manipultionen:
Standard-Zuweisung
Möchte man zwei Variablen aus dem Typ liste
(dessen Daten auf einer Halde liegen), mit
der Standard-Zuweisung q:=p verwenden, wobei an dem Objekt p bereits
ein Knotengeflecht auf der Halde anhängt, so erzeugt man lediglich
einen weiteren Zugriffspfad (Alias) auf das selbe Geflecht. Damit ist
der Versuch fehlgeschlagen ein neues Geflecht
mit dem selben Inhalt zu erzeugen.
Eine Veränderung über einen der beiden Pfade
p und q wird damit auch über den anderen
Pfad sichtbar, da es sich ja um dasselbe Objekt handelt.
Außerdem haben wir, falls q bereits einen
legalen Pointer auf ein Haldenobjekt enthielt,
diesen jetzt überschrieben, und womöglich ist dieses Objekt dadurch
sogar unerreichbar geworden (sofern wir nicht daran gedacht
haben, es freizugeben, wenn es nicht
mehr
benötigt wird).
Wir haben soeben die Referenz-Semantik
kennengelernt,
da die interne Realisierung eines Datenobjektes
ein Zugriffspfad auf ein Geflecht ist.
Abhilfe: Die tiefe Kopie von Listenobjekten
Über eine explizite tiefe Kopie erzeugt man
wirklich ein neues Listenobjekt, dass dann
den selben Inhalt hat wie das Orginal, aber
unabhänging vom anderen verändert werden
darf. Es erhält einen eigenen Zugriffspfad.
Standard-Vergleichsoperationen
Setzen wir jetzt die Vergleichsoperationen
= und /= auf die beiden Listenvariablen
p
und q an, dann vergleichen wir die
Zugriffspfade
und liefern somit die Information,
ob es
sich um dasselbe konkrete Objekt handelt,
nicht aber ob nur die Folgen der Werte
gleich
sind.
Bei der Implementierung in Ada müssen
wir
darauf achten, dass wir keine null-Werte
dereferenzieren dürfen, und dass ein
Objekt
sich selbst gleich ist.
Freigabe von Listenobjekten
Der Speicher, der von Geflechten belegt ist
muss immer explizit freigegeben werden. Eine
spezielle Prozedur Free musss mittels procedure Free is new Ada.Unchecked_Dellocation(knoten,
liste); eingebunden werden sie gibt das Bezugsobjekt
ihres Arguments frei und setzt den
Verweis
auf null. Es ist gute Praxis, jede
neu deklarierte
Variable eines Pointertyps explizit
mit dem
Wert null vorzubesetzen (in Ada und
vielen
objektorientierten Sprachen geschieht
das automatisch).
|