Kurs:Programmieren in Oberon/Kapitel 5
Dieses Kapitel gehoert zum Kurs Programmieren in Oberon des Fachbereichs Informatik.
"I am a bear of little brain, and long words bother me."[1]
[Bearbeiten]Also schön, wir haben gesehen, dass Oberon mit Zeichen noch ganz gut umgehen kann, aber wie steht es mit Wörtern? Wir haben ja schon die Anweisung Out.String() gesehen, welche eine Zeichenkette annimmt und an die Ausgabe schickt. Aber wie steht es, wenn man diese Zeichenketten verarbeiten will? Wie werden sie in Oberon gespeichert?
Ziel dieses Kapitels ist es, ein Modul namens MeinModul, welches eine Prozedur namens Statistik enthält, welche alle Zeichen bis zum ~ liesst und danach eine Statistik ausgibt, welche Zeichen wievielmal vorgekommen sind, schreiben zu können. Für diejenigen unter euch, die sich schon mächtig fühlen, sehen wir uns am Ende des Kapitels.
Worte als Reihen von Buchstaben -- das Array
[Bearbeiten]So wie in jeder Sprache üblich, sind Sätze nichts mehr als eine Reihe von Wörter, Wörter nichts mehr als eine Reihe von Zeichen. Oberon macht keine Ausnahme: will man einen Text repräsentieren, so muss man es als eine Reihe von Zeichen speichern und verarbeiten.
Wie wird aber in Oberon eine Reihe von irgendwas deklariert? Die Lösung heisst ARRAY, eine Liste von Variablen, welche man über ein Index direkt ansprechen kann. Die Deklaration sieht etwa so aus:
VAR kleinerSatz : ARRAY 20 OF CHAR;
Man könnte meinen, kleinerSatz sei jetzt eine Variable, dies ist aber nicht ganz richtig, sie ist eher 20 Variablen vom Typ CHAR. Die Variablen kann man wie folgt ansprechen:
kleinerSatz[0] := "H"; Out.Char(kleinerSatz[0]);
Jedes Element von einem Array verhält sich wie eine eigenständige Variable, welche mit dem Array-Namen mit der Indexzahl in eckigen Klammern angesprochen wird. Wie im Beispiel ersichtlich fängt die Numerierung bei 0 an und geht bis zur Länge des Arrays minus 1. Versucht man in einem Programm über die Länge des Arrays hinaus zu Lesen oder Schreiben, so löst dies ein Trap aus und das Programm stürzt ab.
Arrays ueber mehrere Dimensionen
[Bearbeiten]Arrays dienen nicht nur zur Darstellung von Zeichenketten, sondern können für ziemlich alles gebraucht werden. Eine sehr hilfsreiche Eigenschaft davon ist die Fähigkeit, auch mehrdimensional zu sein. Man kann also zum Beispiel ein Schachbrett wie folgt deklarieren:
VAR brett : ARRAY 8,8 OF CHAR;
wobei die Werte im Array dann wie folgt angesprochen werden:
brett[0,1] := "b";
wobei "b" jetzt einfach ein Bauer darstellen soll. Wir haben also in unserem Schachbrett-Array ein Bauer an der Position 0,1. Die Deklaration eines mehrdimensionalen Arrays kann auch wie folgt angesehen werden, wieder mit dem Schachbrett:
VAR brett : ARRAY 8 OF ARRAY 8 OF CHAR;
Will man nun wieder auf die Variable an der Position 0,1 zugreifen kann man dies natürlich auch so tun:
brett[0][1] := "b";
Will man auch noch die erste Zeile des Brettes als Zeichenkette ansprechen, so kann man dies auch auf folgender Art und Weise erledigen:
Out.String(brett[0]);
Zu beachten ist jedoch, dass für die Ausgabe als Zeichenkette mit Out.String, das Array richtig terminiert werden sollte, was später noch erklärt werden wird.
Arrays hin und her - die Parameteruebergabe
[Bearbeiten]Obwohl die Deklaration und das Behandeln von ARRAYs ziemlich gleich wie die Normaler Variablen geschieht, gibt es einige kleine Ausnahmen, nämlich bei der Parameterübergabe und Werterückgabe von Prozeduren.
Das mit der Werterückgabe ist ziemlich einfach und leicht zu merken: es geht nicht! In Oberon können nur einfache Typen, also INTEGER, CHAR, REAL und Ähnliche zurückgegeben werden, also keine ARRAYs!
Will man einer Prozedur ein ARRAY als Parameter übergeben, so muss man das wie folgt deklarieren:
PROCEDURE Laenge ( string : ARRAY OF CHAR ) : INTEGER;
Dem aufmerksamen Leser wird aufgefallen sein, dasx die Länge des deklarierten ARRAYs bei der Deklaration im Prozedurkopf nicht festgelegt wird. Dies darf man auch nicht. Die Prozedur, so wie sie jetzt deklariert ist, erlaubt die Übergabe von beliebig grossen ARRAYs. Die Überprüfung der Länge hinsichtlich einer Indexüberschreitung ist dem Programmierer überlassen, wobei eine kleine, in Oberon fest eingebaute Prozedur einiges an Abhilfe leistet:
LEN(kleinerSatz); LEN(brett,1);
Die Prozedur LEN liefert für ein beliebiges ARRAY dessen Länge zurück. Gibt man noch einen zweiten Parameter dazu, so liefert es die Länge der Dimension vom Parameter plus 1. LEN(brett,1) liefert also 8 als Rückgabe, da die zweite Dimension von brett gemäss Definition 8 ist.
Will man einer Prozedur ein mehrdimensionales ARRAY als Parameter übergeben, so muss man das zum Beispiel wie folgt tun:
PROCEDURE init ( VAR brett : ARRAY OF ARRAY OF CHAR );
Zu beachten ist nur noch, dass sich die VAR-Anweisung genau gleich wie bei anderen Typen verhält
Arrays als Zeichenketten
[Bearbeiten]Um bequem mit Zeichenketten herumhantieren zu können, stellt uns Oberon einige kleine Hilfsmittel zur Verfügung. Will man zum Beispiel ein ARRAY OF CHAR mit den Worten "Hallo, Niklaus!" füllen, so kann man das wie folgt tun:
kleinerSatz := "Hallo, Niklaus!";
Ja, so einfach geht das. Schaut man sich jetzt nach dieser Anweisung kleinerSatz genauer an, so sieht man folgendes:
H | a | l | l | o | , | N | i | k | l | a | u | s | ! | 0 |
Die Zeichen "Hallo, Niklaus!" werden reingeschrieben und mit einer 0 terminiert. Diese 0 ist jedoch nicht der Buchstaben "0" sondern das Zeichen mit dem Wert 0. Dieses wird in Oberon als 0X dargestellt. Er dient dazu, allen Prozeduren, die was mit Zeichenketten machen, zum Beispiel Out.String, zu sagen, dass die Zeichenketten dort aufhören. So druckt Out.String(kleinerSatz) alle Zeichen von kleinerSatz aus bis es auf ein 0X stösst. Die restlichen Zeichen, die eigentlich keine Information enthalten, werden nicht gedruckt.
Baut man sich also selber eine Zeichenkette zusammen, ist es wichtig, das letzte Zeichen auf 0X zu setzen, damit man das Ding auch mit den Standardprozeduren verarbeiten kann.
Es werden sich jetzt noch einige gefragt haben, was ist "ein Zeichen mit dem Wert 0"? Jeder Computer versteht bekanntlich nur Nullen und Einsen und baut sich daraus Zahlen und rechnet nur mit diesen. Wie soll er also nun mit Zeichen umgehen? Ganz einfach: er interpretiert Zeichen als Zahlen zwischen 0 und 255, jedem Zeichen wir eine Zahl zugeordnet.
Will man wissen, welche Ordnungszahl ein Zeichen hat, kann man die in Oberon eingebaute Prozedur
ORD(c);
aufrufen, welche für ein CHAR c dessen Ordnung als INTEGER zurückgibt. Die Umkehrung dieser Prozedur heisst
CHR(i);
welche für ein INTEGER i zwischen 0 und 255 dessen Zeichen als CHAR zurückgibt.
Satzlogik
[Bearbeiten]Man kann nicht nur diese Zeichenketten bequem initialisieren, man kann sie auch ohne weiteres miteinander vergleichen oder aufeinandersetzten. Einige Beispiele sind
einWort := "Hallo"; kleinerSatz := einWort; IF einWort = kleinerSatz THEN ... END; IF einWort < "Niklaus" THEN ... END;
Die Bewertung von den < und > Operatoren geschieht vom Index 0 an anhand der Ordnung jedes Buchstaben. Zu bemerken ist nur noch, dass
"a" > "A"
Zeichen einlesen -- dem Computer das Lesen beibringen
[Bearbeiten]Also gut, wir wissen, dass in Oberon Zeichen nichts anderes sind als ARRAYs von Zeichen. Wir haben auch schon im dritten Kapitel gelernt, wie man einzelne Zeichen einlesen kann. Jetzt tun wir so als wären wir ganz gescheit und vermischen beides: wir schreiben eine Prozedur, welche ein ARRAY OF CHAR bekommt, und dieses mit den Zeichen der Eingabe bis zum Tilde füllt und mit einem 0X abschliesst. Zudem gibt er noch die Länge des ARRAYs als Rückgabe zurück.
PROCEDURE LeseEingabe ( VAR ein : ARRAY OF CHAR ) : INTEGER; VAR pos : INTEGER; c : CHAR; BEGIN In.Open; pos := o; In.Char(c); WHILE c # "~" DO ein[pos] := c; INC(pos); In.Char(c); END; ein[pos] := 0X; RETURN pos; END LeseEingabe;
Ist doch toll, nicht? Wir lesen die Eingabe Zeichen für Zeichen ein, testen ob es kein Tilde ist, brechen bei einem solchen ab, sonst wird das eingelesene Zeichen ins ARRAY geschrieben und das nächste eingelesen. Vielleicht wollt ihr diesen letzten Satz noch einmal lesen bevor ihr's einfach hinnehmt...
Will man zum Beispiel einzelne Worte einlesen, so muss man zuerst alle vorangehende Leerschläge ignorieren, das Wort dann Zeichen für Zeichen einlesen und auf einen Leerschlag warten. In Oberon sieht das dann etwa so aus:
PROCEDURE LeseWort ( VAR ein : ARRAY OF CHAR ) : INTEGER; VAR pos : INTEGER; c : CHAR; BEGIN In.Open; In.Char(c); WHILE c = " " DO In.Char(c); END; pos := o; WHILE c # " " DO ein[pos] := c; INC(pos); In.Char(c); END; ein[pos] := 0X; RETURN pos; END LeseWort;
Jetzt gibt es noch ein kleines Problem: der erste Leerschlag nach dem Wort wird eingelesen, unser "Textzeiger" rückt um eins nach vorne. Haben wir eine andere Prozedur, die zum Beispiel Zahlen für ein Mathematikprogramm einliest, so liest sie eine Zahl bis zu einem "*" (Multiplikator) ein, den sie, wie bei uns der nächste Leerschlag, auch mit der Zahl einliest, aber dann, weil c lokal ist, beim Verlassen der Prozedur fortwirft. Wie weiss man was nach der Zahl gekommen ist?
Man kann dieses Problem auf zwei Arten lösen: entweder man deklariert die Variable c global und verzichtet auf das erste In.Char(c), welche dann im Modulkörper oder sonstwo aufgerufen wird, oder wir kriegen noch als Parameter das erste Zeichen, verzichten auf das erste In.Char(c), und geben entweder das zuletzt gelesene CHAR als Rückgabe zurück oder setzen ein VAR vor dem Parameter.
Wenn ihr das Problem nicht gerade versteht, ist es nicht so schlimm, hauptsache ihr könnt Zeichen als Zeichenketten einlesen.
Des Raetsels Loesung
[Bearbeiten]Nun gut, zurück zur Aufgabenstellung: wir wollen kein ARRAY von Zeichen einlesen, sondern die Zeichen eins nach dem anderen zählen und schauen, wieviel wir am Schluss von jedem haben.
MODULE MeinModul; IMPORT In, Out; VAR data : ARRAY 256 OF INTEGER; PROCEDURE Statistik*; VAR c : CHAR; total : INTEGER; BEGIN FOR total := 0 TO 255 DO data[total] := 0; END; total := 0; In.Open; In.Char(c); WHILE c # "~" DO INC(data[ORD(c)]); INC(total); In.Char(c); END; Out.String("total Zeichen: "); Out.Int(total,3); Out.Ln; FOR total := 0 TO 255 DO IF data[total] # 0 DO Out.Int(total,3); Out.String(" '"); Out.Char(CHR(total)); Out.String("' : "); Out.Int(data[total],3); Out.Ln; END; END; END Statistik; BEGIN Out.Open; END MeinModul.
Wie gesagt bewegen sich die Werte der Zeichen zwischen 0 und 255, also speichern wir doch die Anzahl von jedem Zeichen in ein ARRAY 256 OF INTEGER. Diese initialisieren wir brav am Anfang mit einer... haeh?
"WHILE" for Dummies -- die FOR-Schleife
[Bearbeiten]Naja, schon meint man, man verstehe schon alles, dann kommt doch sowas blödes. Verzweifelt aber nicht, denn die Handhabung dieses Dings ist unglaublich einfach (hört sich an wie eine Fitness-Geräts-Werbung)!
FOR i := start TO ende DO ... END;
Da wir schon Experten in der Handhabung von WHILE-Schleifen sind, mache ich da zur Erleuchtung eine kleine, vollkommen gleichwertige, Übersetzung:
i := start; WHILE i <= ende DO ... INC(i); END;
Ziemlich einfach, oder? FOR zählt von start bis ende, setzt jeweils i auf diesem Wert und durchläuft die Schleife. Ab und zu sieht man auch sowas
FOR i := start TO ende BY schritt DO ... END;
Dies ist wiederum gleichwertig zu
i := start; WHILE i <= ende DO ... INC(i,schritt); END;
Will man also start bis ende in 10er-Schritte rückwärts durchlaufen, so setzt man schritt auf -10. Zu bemerken ist vielleicht nur noch, dass schritt entweder eine Zahl oder ein als CONST deklarierter Wert sein kann. Oberon ist zwar schon raffiniert, ist aber immer noch nicht fähig, einen variablen Schritt anzunehmen. Naja.
Fussnoten
[Bearbeiten]Fussnote, s, f: Esthetikmass in der Podophilie. "Kate Blanchet verdient meines Erachtens eine Fussnote von nur einer 3".
- ↑ Aus Winnie the Pooh (zu Deutsch "Pu der Baer"), von A. Milne, 1926.