Kurs:Programmieren in Oberon/Kapitel 2
Dieses Kapitel gehoert zum Kurs Programmieren in Oberon des Fachbereichs Informatik.
Jetzt macht ihr was
[Bearbeiten]Das erste Programm hat (hoffentlich) funktioniert und ihr habt sicher damit herumgespielt. Ich wette, einige von euch haben es sogar schon umfunktioniert, damit es Obszönitäten ausspuckt. Jetzt wollen wir aber etwas sinnvolleres machen, nämlich …
Ein Programm, das auf 10 zählen kann
[Bearbeiten]Das klingt nicht blöd, das klingt bescheuert. Aber damit man sowas kann, muss man zwei ausserordentlich wichtige Komponenten der Programmierung verstehen: Variablen und Schleifen.
Es hat sicher viele unter euch die diese Dinge schon kennen, also machen wir folgendes: Ziel dieses Kapitels ist es ein Modul namens MeinModul zu schreiben, welches eine Prozedur namens ZaehlMal enthält, welche beim Ausführen die Zahlen 1 bis 10 auf dem Bildschirm darstellt und am Schluss noch einen Zeilenumbruch einfügt. Am Ende des Kapitels wird ein Beispiel eines solchen Moduls stehen, das als Musterlösung dienen soll.
Variablen, oder „wofür man RAM braucht“
[Bearbeiten]Variablen kennen wir (hoffentlich) aus der Mathematik: Dort benutzen wir oft Kleinbuchstaben oder sonstige Zeichen um Zahlen oder Funktionen darzustellen, die wir nicht kennen. In der Programmierung ist es genau umgekehrt: Variablen repräsentieren das, was wir wissen.
Jede Variable speichert Daten in irgendeiner Form. Es gibt Variablen für ganze Zahlen, Fliesskommazahlen, Buchstaben oder strukturierte Daten wie ARRAYs oder RECORDSs.
Wichtig ist bei den Variablen, dass sie ihre Form behalten. Eine Variable für ganze Zahlen wird immer nur ganze Zahlen beinhalten. Deklarieren wir eine Variable für einen Buchstaben, so werden immer nur Buchstaben darin zu finden sein.
Die wichtigsten Variablentypen in Oberon heissen wie folgt
Type Beschreibung Beispiel INTEGER Ganze Zahl zwischen -32767 und 32768. 1, -1438, 0 LONGINT Grosser Bruder von INTEGER, von -2147483647 bis 2147483648. 1, -1438, 0 REAL 32-bit-Gleitkommazahl, im Wesentlichen einfach eine reelle Zahl. 0.0, -1.9824, 1.0e12 LONGREAL 64-bit-Variante von REAL. 0.0, -1.9824, 1.0e12 CHAR Ein einzelner Buchstabe. 'a', 'Z'
Dies sind die sogenannten Basistypen. Warum wir ihnen überhaupt einen speziellen Namen geben, werden wir spaeter sehen.
Die Geburt einer VAR
[Bearbeiten]Bevor man eine Variable in Oberon brauchen kann, muss sie zuerst deklariert werden. Dies geschieht etwa so:
VAR i : INTEGER;
wobei i den Namen der Variablen darstellt und INTEGER den Typ. Man kann natürlich in einer VAR-Anweisung mehrere Variablen deklarieren, und zwar wie folgt:
VAR i, j : INTEGER; r : REAL; c : CHAR;
Die häufigste Frage ist, wohin man diese nette Deklaration eigentlich schieben soll. Es gibt zwei Möglichkeiten: ins Modul oder in eine Prozedur. Die VAR-Anweisung steht in den Prozeduren zwischen der PROCEDURE-Anweisung und dem BEGIN, bei den Modulen zwischen dem IMPORT und der ersten PROCEDURE. Das ganze sieht etwa so aus:
MODULE EinModul; IMPORT Out; VAR i, j : INTEGER; PROCEDURE MachWas*; VAR c : CHAR; BEGIN ... END MachWas; BEGIN ... END EinModul.
Scope – die Reichweite einer Variablen
[Bearbeiten]Da wir, wie gesehen, unsere Variablen an zwei verschiedenen Orten deklarieren können, stellt sich die einfache Frage nach dem Unterschied.
Deklariert man eine Variable im Modul (also ganz oben unter dem IMPORT), so ist diese Variable für alle Prozeduren des Moduls sichtbar und brauchbar. Jede Prozedur kann den Wert dieser Variablen lesen oder setzen.
Ist eine Variable jedoch in einer Prozedur deklariert, so beschränkt sich ihre Existenz auf die Prozedur selbst: man kann die Variable von aussen nicht verändern. Geschieht es noch, dass man in einer Prozedur eine Variable deklariert, deren Name schon im Modul deklariert wurde, so gilt in der Prozedur nur die neu deklarierte Variable, die andere wird ignoriert und nicht verändert. Ein kleines Beispiel:
MODULE EinModul; IMPORT Out; VAR a, b : INTEGER; PROCEDURE ErsteProzedur*; VAR b, c : INTEGER; BEGIN a := 1; b := 1; c := 1; Out.Int(a,5); Out.Int(b,5); Out.Int(c,5); END ErsteProzedur; PROCEDURE ZweiteProzedur*; BEGIN Out.Int(a,5); Out.Int(b,5); Out.Int(c,5); (* geht nicht! *) END ZweiteProzedur; BEGIN Out.Open; a := 0; b := 0; END EinModul.
Kompiliert man das Ganze, erscheint bei der Anweisung
Out.Int(c,5);
eine Fehlermeldung. Dies ist Absicht und kein Bug. Ich wollte damit nur zeigen, dass die Variablen von ErsteProzedur in ZweiteProzedur nicht ansprechbar sind.
Wenn man jetzt (nach Korrektur und Rekompilierung) zuerst ErsteProzedur aufruft, meldet das Modul wie erwartet
1 1 1
a, b und c wurden in ErsteProzedur auf 1 gesetzt. Führt man jetzt noch ZweiteProzedur aus, so erscheint aber als Resultat dazu
1 0
Für die Variable a ist der Wert 1 klar, er wurde in ErsteProzedur gesetzt, was ist aber mit b? Wurde er nicht in ErsteProzedur auch auf 1 gesetzt? Nein. Da in ErsteProzedur auch noch eine Variable b deklariert wurde, wurde diese, statt die im Modul definierte, auf 1 gesetzt. Das b vom Modul blieb unverändert.
Die Variablen im Modul nennt man „global“, die in den Prozeduren „lokal“.
Schleifen -- ueber die Monotonie des Seins
[Bearbeiten]Die wahrscheinlich bekannteste Schleife bei den Informatikern (naja, bei einigen wenigstens) lautet: "while not drunk, drink more beer". Diese kleine Lebensweisheit erklärt den Geist einer Schleife bestens: man soll irgendeine Tätigkeit wiederholen, bis eine gegebene Bedingung nicht mehr erfüllt ist.
Für den Computer, der übrigens kein Bier trinken kann, beschränkt sich die Tätigkeit auf einen Haufen Anweisungen die in der Schleife stehen, und die Bedingung auf einen Ausdruck welcher entweder auf TRUE oder FALSE resolviert (siehe Logik).
In Oberon gibt es drei Schleifen und eine pseudo-Schleife. Eine der "echten" Schleifen sieht etwa so aus
WHILE Bedingung DO ... END;
In Jedermannssprache heisst dies soviel wie: "gilt Bedingung, so führe die Anweisungen zwischen DO und END aus und fange wieder von vorne an". Ist Bedingung immer TRUE, so hört die Schleife nie auf und man muss auf die Ctrl-Shift-Del-Kombination zugreifen um den Computer gewaltsam wieder in den Griff zu kriegen. Es wäre also wünschenswert, dass die Anweisungen zwischen DO und END irgendwas an der Bedingung ändern, so im Sinn von einem Zähler erhöhen oder sowas ähnlichem.
Der Vorgaenger zur WHILE-Schleife ist der etwas pimitivere LOOP (fuer nicht-anglophile heisst das einfach "Schleife"):
LOOP ... END;
Das Fehlen jeglicher Bedingung laesst schon ahnen, dass diese Schleifenvariante ohne einen zusaetzlichen Trick, den ich bis jetzt vorenthalten habe, nie abbricht. Der Trick heisst EXIT. Will man einen LOOP analog zu obiger WHILE-Schleife basteln, so wuerde das etwa so aussehen:
LOOP IF ~Bedingung THEN EXIT END; ... END;
Man beachte, dass die Bedingung geprueft wird, bevor wir ueberhaupt den Schleifeninhalt ausfuehren. Will man das aus irgendwelchen Gruenden umgekehrt machen, naemlich
LOOP ... IF Bedingung THEN EXIT END; END;
So gibt es auch eine praktischere Umschreibung dafuer, naemlich die REPEAT-Schleife:
REPEAT ... UNTIL Bedingung;
Der Schleifeninhalt wird wiederholt, bis Bedinung eintritt.
Jetzt zaehl doch endlich!
[Bearbeiten]Also gut, jetzt kennen wir die zwei wahrscheinlich wichtigsten Bauteile der ganzen Informatik, wenden wir sie doch mal an und bringen dem Computer bei, auf 10 zu zählen!
Eigentlich könnten wir jetzt ganz daemlich sein und ein Programm mit zehn Out.Int-Anweisungen zusammenschmeissen, welches so aussieht als ob es auf 10 zählen wurde. Das wäre aber wie gesagt ziemlich daemlich und wäre auch kein richtiges Zählen.
Überlegen wir jetzt mal, ganz simple, wie wir selber zählen: Wir fangen irgendwo bei einer Zahl an, brüllen es raus, zählen eins dazu, und so weiter bis wir bei der zu zählenden Zahl angekommen sind. Der Computer soll genau das gleiche machen:
WHILE zahl <= 10 DO Out.Int(zahl,5); zahl := zahl + 1; END;
Sieht doch ziemlich einfach aus, nicht? Einzig fehlt noch, dass wir die Variable zahl auf irgend ein Wert initialisieren müssen, bevor wir in die Schleife gehen. Auch muss zahl noch irgendwo deklariert werden und das Ganze zu einem funktionierenden Modul verpackt werden. Das ganze gemäss Aufgabenstellung sieht bei mir jetzt wie folgt aus:
MODULE MeinModul; IMPORT Out; PROCEDURE ZaehlMal*; VAR zahl : INTEGER; BEGIN zahl := 1; WHILE zahl <= 10 DO Out.Int(zahl,5): zahl := zahl + 1; END; Out.Ln; END ZaehlMal; BEGIN Out.Open; END MeinModul.
Wie gesagt, ist das meine Version des Moduls und nicht die einzig mögliche Lösung. Wichtig ist, dass das Programm von 1 bis 10 zählt und dies nicht auf allzu komplizierte weise tut.