Logo von Mathlab.de
Grundlagen der Informatik I
-- One person's constant is another person's variable --

- 1.1 Kontrollstukturen in Ada95
- 1.2 Prozeduren in Ada95
- 1.3 Vergleich Array und Liste
- 1.4 Datenstrukturen in der Programmierung
- 1.5 Hinweise zum Programmierstiel
- 1.6 Besonderheiten in Ada 95



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.



hoch

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;


hoch

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).


hoch

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;


hoch

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


hoch

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.


hoch

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).

hoch

 
 
zurück Übersicht Informatik