Claus SchönleberHitchhacker´s Guide To PASCAL [Vol. 1] |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
[ Start | Beispielprogramme Download | Home ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
InhaltCompilerProgrammieren Datentypen, Variablen, Standardfunktionen Logik Verzweigung, Strukturierung Schleifen Felder (Arrays) Verteiler: CASE Zeichenketten (Strings) Textdateien Module (Prozeduren, Funktionen) Anhang (Operatoren, abgeleitete Funktionen)
|
Vereinbarung
Grundstruktur eines PASCAL-Programms
Dabei bedeutet <identifier> einfach einen Namen, der das Programm
benennen soll; der <block> besteht aus einem Deklarationsteil
(hier werden notwendige Strukturen vorgestellt) und einem Anweisungsteil
(hier findet die eigentliche Arbeit statt). Der Anweisungsteil ist zwischen
den Worten BEGIN und END eingeschlossen. Demnach muß ein PASCAL Programm
mindestens so aussehen:
Im Deklarationsteil vereinbart man alle Datenstrukturen, die man im Laufe des Programms benötigt. Warum das? PASCAL ist eine Compilersprache. In vorübersetzten Programmen müssen benutzte Strukturen angemeldet werden, bevor sie benutzt werden können. In interpretierten Sprachen (z.B. Smalltalk) ist das nicht nötig. Hier können die Strukturen während der Laufzeit des Programms flexibel mit unterschiedlichem Inhalt gefüllt werden. Den <modulteil> werden wir noch weitestgehend außer Acht lassen. Er wird im Kapitel "PASCAL-Module" ausführlich besprochen werden. Datentypen und VariablenDa Compilersprachen wissen müssen, womit sie während der Laufzeit umgehen sollen, sind die Daten zu deklarieren. Dies wird durch drei Eigenschaften definiert:
AdressraumAn dieser Stelle soll erklärt werden, warum so vielfältig geartete Problemlösungen mit so vielerlei verschiedenen Datenstrukturen alle in der höchst simplen Anordnung des Arbeitsspeichers Platz haben und funktionieren. Ein Speicher ist ein eindimensionales Gebilde. Eindimensional heißt, alle Speicherzellen liegen hintereinander in einer Reihe, alle Speicherzellen sind durchnumeriert. Diese Art der Linearität hindert jedoch nicht daran, daß andere Formen der Anordnung benutzt werden können oder auch müssen. Zum Beispiel kann man mit Leichtigkeit ein Schachbrett im Speicher simulieren. Wie jeder weiß, ist ein Schachbrett eine Fläche. Die Tatsache, daß ein zweidimensionales Gebilde (Fläche) und sogar noch kompliziertere "räumliche" Anordnungen in dem scheinbar eindimensionalen Speicher realisiert werden können, liegt an der Tatsache, daß man jede Speicherzelle mit jeder anderen durch ein entsprechendes Programm verbinden kann.Dadurch ergibt sich ein grundlegender Unterschied zwischen der Architektur der Hardware im Speicher und der Architektur der Daten in möglichen Programmen. Man sagt, die Anordnung des Hardwarespeichers bildet den Adressraum der Maschine, die Anordnung der Daten im Programm bilden einen anderen, nämlich den Adressraum des Programms. Beide Adressräume können, müssen aber nicht identisch sein, aber beide Räume müssen sich in irgendeiner Weise durch das Programm ineinander überführen lassen.
Deklaration von VariablenWir können uns jetzt daran machen, nach der oben erwähnten Grundstruktur für ein erstes Beispiel eines einfachen PASCAL-Programms den Deklarationsteil zu konkretisieren. Eingeleitet wird der Variablendeklarationsteil mit dem Bezeichner VAR:
Mit diesem PASCAL Quelltext wird der Compiler aufgefordert, im Speicher
der Hardware nach freiem Speicherplatz zu suchen, wo eine ganze Zahl untergebracht
werden kann. Das sind üblicherweise mehrere Byte im Adressraum der
Maschine. Im Adressraum des Programms ist es hingegen eine Speicherzelle.
Danach wird in einer internen Liste der gewählte Name ("zahl") und
die wirkliche, vom Compiler gefundene Speicheradresse eingetragen. Wann
immer im Anweisungsteil eine Referenz auf den Namen "zahl" auftaucht, wird
anhand der Liste auf diesen einen Speicherplatz zugegriffen, unabhängig
davon, welcher spezielle Wert darin enthalten ist!
In Borlands Turbo PASCAL ab Verson 4.0 treten die Typen integer und real noch variiert auf, indem die Bereichsgrenzen nach oben oder unten verschoben werden. Nachzulesen im entsprechenden Handbuch. TeilbereichtypenManchmal braucht man nicht den gesamten von PASCAL zur Verfügung gestellten Bereich eines Typs. Dann kann man einen Teilbereich definieren. Das funktioniert sehr einfach und ist in verschiedenen Situationen möglich. Sogar sehr moderne Sprachen kennen diesen Mechanismus leider nicht.Immer, wenn man einen Teilbereich benutzen will, gibt man ihn in folgender Form an: <min>..<max>
Es kommt auf den Compiler an, ob er Bereichsüberschreitungen bei Variablen mit solchen Typen prüft oder nicht. Turbo PASCAL läßt dem Programmierer die Wahl. Man kann die Prüfoption anschalten oder weglassen, je nach Bedarf oder Lust und Laune. TypdeklarationenMan kann sich auch eigene Typen definieren. Zum Beispiel für ganze Zahlen oder für das Alphabet. Auch vollständig neue Typen können "erfunden" werden. Eine Typendeklaration erfolgt vor der Variablendeklaration.Die Form einer Typendeklaration sieht so aus:
Man benutzt das, um die Einführung eines Standardtyps oder Teilbereichs zu rechtfertigen. Statt zum Beispiel folgende Variablendeklaration zu schreiben, kann folgende Typendeklaration viel einleuchtender sein:VAR x,y,summe,l : integer; Man kann auch vollständig neue Typen definieren:TYPE coordinaten = integer; VAR x,y : coordinaten; summe,l : integer; TYPE blume = (rose,nelke,veilchen,aster,lilie); TYPE computer = (sun,apple,ibm); TYPE auto = (karosserie,raeder,motor); TYPE himmelsrichtung = (Nord,Ost,Sued,West); TYPE mahlzeit = (fruehstueck,mittagessen,abendbrot);Dabei muß man in den Klammern die zugelassenen Werte alle einzeln aufführen. Durch die Reihenfolge der Liste ist automatisch eine Ordnung (links kleiner als rechts) definiert. Die Operationen succ (Nachfolger) und pred (Vorgänger) sind auf solche Typen anwendbar. Konstanten aus selbstdefinierten Typen können weder über die Tastatur eingegeben, noch auf dem Bildschirm direkt ausgegeben werden, es sei denn, man schreibt sich dafür eigene Ein-/Ausgabeprozeduren, was manchmal durchaus empfehlenswert ist. Es ist jedoch auf jeden Fall sehr bequem, im Bedarfsfall damit zu rechnen. Selbstdefinierte Typen werden ansonsten behandelt wie einfache Standardtypen. Die Konstanten in so einem Typ sind keine Zeichenketten! Sie sind genauso unteilbar wie die Zahl 15, der Wahrheitswert true oder der Buchstabe 'A'. KonstantenOft braucht man immer wiederkehrende, sich nicht verändernde Werte in einem Programm. Dafür gibt es die Einrichtung der Konstante. Sie unterscheidet sich eigentlich überhaupt nicht von einer Variablen. Sie dient nur der Aufzählung solcher Werte in einem gesonderten Rahmen. Konstanten darf im Normalfall während der Laufzeit des Programms kein Wert zugewiesen werden.Sie werden mit dem Wort CONST im Deklarationsteil eingeführt und
sollten als erstes definiert werden. Die übliche Reihenfolge ist zwar
CONST, TYPE, VAR, aber nicht verbindlich in den meisten Implementationen.
Sie sollte trotzdem eingehalten werden.
Durch die Form der Konstanten erfolgt eine automatische Typklassifizierung: CONST pi = 3.14159265; (* real *) CONST name = 'Herr Meier'; (* STRING *) CONST blank = ' '; (* char *) CONST istwert = true; (* boolean *)Die Namen stellen keine Variablen dar, sondern werden vom Compiler durch Zeichenkettenersetzung beim übersetzen durch den dahinterstehenden Wert ausgetauscht.
Es gibt das auch in PASCAL! Man kommt gut ohne aus!
Sie stören nur bei einer Einführung! Sie sind sehr einfach zu
handhaben! Sie verleiten meistens zu schlimmen, unübersichtlichen
Konstruktionen! Wer PASCAL kann, ist ohne weiteres in der Lage, sie sich
anzueignen und gewinnbringend zu nutzen! In diesem Buch werden, bis auf
eine Ausnahme, keine Labels verwendet. Die Ausnahme ist CASE (siehe unten).
|
integer/real | + | Addition |
integer/real | - | Subtraktion |
integer/real | * | Multiplikation |
integer | DIV | ganzzahlige Division mit Rest (modulo) |
integer | MOD | Rest bei ganzzahliger Division |
real | / | Division |
char | ord () | Wert nach ASCII-Tabelle |
char | chr () | Zeichen nach ASCII-Tabelle |
boolean | NOT | logisches NICHT |
boolean | OR | logisches ODER |
boolean | AND | logisches UND |
|
Darüberhinaus gibt es noch weitere Funktionen, die Daten aus den
verschiedenen Typen erzeugen. In der nachstehenden Tabelle sind sie aufgeführt.
Unter "einf. Typ" (einfacher Typ) sollen die Typen integer, boolean, char
und weitere selbstdefinierte Typen verstanden werden.
Erklärung | Name | Eingabetyp | Ausgabetyp |
Nachfolger | succ () | einf. Typ | einf. Typ |
Vorgänger | pred () | einf. Typ | einf. Typ |
ist ungerade | odd () | integer | boolean |
Absolutbetrag | abs () | real, integer | real, integer |
Quadrat | sqr () | real, integer | real, integer |
Ganzzahliger Anteil | trunc () | real | integer |
Runden | round () | real | integer |
Quadratwurzel | sqrt () | real, integer | real |
Sinus | sin () | real, integer | real |
Cosinus | cos () | real, integer | real |
Arcus Tangens | arctan () | real, integer | real |
Natürlicher Logarithmus | ln () | real, integer | real |
e-Funktion | exp () | real, integer | real |
Zufallszahl | random () | real, integer | real, integer |
Nachkommaanteil | frac () | real | real |
Ganzz. Anteil | int () | real | real |
|
Die Tabelle zeigt nur arithmetische Funktionen. Daneben gibt es noch viele Funktionen und Prozeduren, die zum Beispiel auf Zeichenketten angewendet werden oder logische Operationen. Diese werden bei Bedarf an entsprechender Stelle erwähnt oder können im Handbuch nachgelesen werden.
Zum Abschluß noch einige wichtige Operatoren, die Vergleiche zulassen:
Äquivalenz; Gleichheit | = |
Antivalenz, Ungleichheit | <> |
Größer als | > |
Kleiner als | < |
Größer oder Gleich | >= |
Kleiner oder Gleich | <= |
|
Das Ergebnis von Vergleichsoperationen ist immer vom Typ boolean, als Ausgangstypen sind integer, real, char, boolean, STRING, sowie alle einfachen, selbstdefinierten Typen erlaubt.
Der gravierende Unterschied zwischen Prozeduren und Funktionen besteht darin, daß eine Funktion nur Bestandteil eines Ausdrucks (expression) sein kann (da ein Wert zurückgegeben wird), eine Prozedur dagegen eine selbständige Anweisung (statement) darstellt.
Genauso wie es vordefinierte Funktionen gibt, wie wir oben gesehen haben, existieren auch vordefinierte Prozeduren. Vier dieser Standardprozeduren spielen eine ausnehmend wichtige Rolle; sie sollen deswegen hier besprochen werden:
Die Eingabeanweisungen read und readln, sowie die Ausgabeanweisungen write und writeln.
Um uns viele Worte zu sparen, werden wir sie einfach in unser noch nicht
ganz fertiges Programm einbauen.
PROGRAM erstes_beispiel; VAR zahl : integer; BEGIN write ('Geben Sie eine Zahl ein: '); readln (zahl); writeln ('Danke!'); writeln ('Die eingegebene Zahl lautet: ',zahl); END. |
|
Zugegeben, dies Beispiel sieht nicht sehr sinnvoll aus; es zeigt aber
auf den zweiten Blick eine ganze Menge. Wie sieht das Ganze auf dem Bildschirm
aus, wenn man das Programm übersetzt hat und startet?
Bildschirmausgabe | Erläuterung |
Geben Sie eine Zahl ein: _ |
Das Programm schreibt die in der Prozedur angegebene Zeichenkette auf den Bildschirm, der Cursor steht direkt dahinter. An dieser Position geben Sie nun eine beliebige Zahl ein. |
Geben Sie eine Zahl ein: |
Eingabe von 23 |
Danke! Die eingegebene Zahl lautet: 23 |
Nach der Eingabe der Zahl wird die zweite Zeichenkette ausgegeben und in der Zeile danach die dritte, zusammen mit der Zahl selber. |
|
Vielleicht haben Sie den Unterschied zwischen write und writeln schon bemerkt. Die Silbe "ln" deutet es an. "writeln" ist nicht die bayrische Version von write; es bedeutet, daß nicht nur die angegebene Zeichenkette ausgegeben wird, sondern hinterher noch ein Zeilenvorschub mit Wagenrücklauf ausgeführt wird. "ln" bedeutet daher "line". Gesprochen wird "writeln" als "write line", was soviel bedeutet wie: "Schreibe eine komplette Zeile".
Noch einmal tabellarisch:
write () Schreibt die in der Klammer stehende Zeichenkette auf den Bildschirm, der Cursor steht danach direkt dahinter. Außer Zeichenketten können auch Ausdrücke ausgegeben werden. Die einzelnen Komponenten werden dann durch Kommata getrennt.
writeln () Wie write; nach der Ausgabe aller Komponenten wird ein Zeilenvorschub und ein Wagenrücklauf durchgeführt; der Cursor steht danach in der ersten Spalte der nächsten Zeile. Außer Zeichenketten können auch Ausdrücke ausgegeben werden. Die einzelnen Ausdrücke werden dann durch Kommata getrennt.
read () Mit Hilfe dieser Prozedur können einer oder mehreren Variablen Werte über die Tastatur zugewiesen werden. Verschiedene Variablen werden in der Klammer durch Kommata, Eingabewerte durch Leerzeichen getrennt.
readln () Wie oben; liest eine komplette Zeile von Konstanten, die durch Leerzeichen getrennt sind, über die Tastatur ein. Der eigentliche Unterschied zwischen read und readln ergibt sich erst bei der Benutzung mit Dateien.
Da wir schon eine geraume Zeit über Zeichenketten geredet haben, wird es Zeit, sie genau zu defininieren.
Strings werden üblicherweise auf folgende Art im Hardwarespeicher
dargestellt: Jedes Zeichen einer Zeichenkette wird in ein Byte des Maschinenadressraumes
gepackt. Die Länge der Kette wird in einem Extrabyte notiert, das
an den Anfang der Kette gesetzt wird. Sehen wir uns daraufhin die Speicherdarstellungen
im Adressraum der Maschine einer "normalen" Kette, der Kette "Leerzeichen"
und der Kette "Leere Kette" an.
|
'ABC' | ||||
|
' '
(Leerzeichen) |
||||
|
''
(Leere Zeichenkette) |
||||
|
Das Leerzeichen ist wie jedes andere Zeichen ein vollwertiges Mitglied des Zeichensatzes. Der Leerstring dagegen ist als Neutrales Element der Menge der Zeichenketten bezüglich der Verkettung von Strings zu betrachten. Er ist mit der Null bei der Addition zu vergleichen. Die leere Zeichenkette spielt eine sehr wichtige Rolle bei der Manipulation von Zeichenketten.
PROGRAM mult; VAR zahl1,zahl2 : integer; BEGIN write('Geben Sie zwei Zahlen ein :'); readln(zahl1,zahl2); writeln(zahl1,' * ',zahl2,' = ',zahl1 * zahl2); END.Dieses Programm zeigt sehr schön, wie man mit der write- bzw. writeln-Prozedur umgeht. Vor allem die writeln-Anweisung, die das Ergebnis auf dem Bildschirm ausgibt, zeigt, wie man konstante Zeichenketten und Variableninhalte zu einer komfortablen Ausgabe kombinieren kann. Nehmen wir an, es seien die beiden Zahlen 5 und 23 eingegeben worden, dann erscheint auf dem Bildschirm folgende Ausgabezeile:
5 * 23 = 115
PROGRAM steuer; VAR netto,brutto : real; BEGIN write ('Nettobetrag: '); readln (netto); brutto := netto * 1.16; writeln ('Bruttobetrag: ',brutto,' Euro'); END.Hier wird das Ergebnis nicht sofort ausgegeben, sondern zunächst in einer zusätzlichen Variablen gespeichert und erst dann per writeln-Prozedur ausgegeben. Das ist zwar auf den ersten Blick ein Umweg, ermöglicht aber die Weiterverwertung des Ergebnisses, was andernfalls nicht möglich wäre.
Wie Sie im übrigen sehen, wird die Dezimaltrennung in PASCAL nicht mit dem Komma, wie bei uns üblich, sondern mit der angelsächsischen Methode, nämlich mit einem Punkt vorgenommen. Denken Sie daran!
PROGRAM mult2; VAR zahl1,zahl2 : integer; BEGIN write('Geben Sie zwei Zahlen ein :'); readln(zahl1,zahl2); writeln(zahl1,' * ',zahl2,' = ',zahl1 * zahl2:8); END.Mit dem Zusatz ":8" reserviert man für die Ausgabe 8 Leerstellen ab der Ausgabeposition. Sie werden von rechts her aufgefüllt, links nicht verwendete Stellen werden mit dem Leerzeichen gefüllt. Das kann man sehr bequem für Tabellen nutzen. Daten werden auf diese Weise rechtsbündig ausgegeben.
PROGRAM steuer2; VAR netto,brutto:real; BEGIN write ('Nettobetrag: '); readln (netto); brutto := netto * 1.16; writeln ('Bruttobetrag: ',brutto:8:2,' Euro'); END.Für Ausgaben vom Typ "real" braucht man selbstverständlich zwei Angaben; die Stellen vor dem Komma und die Stellen nach dem Komma. Mit der Direktive im Beispiel haben wir 8 Stellen reserviert, davon 2 Stellen hinter dem Komma.
Diese Methode kann nur in write- bzw. writeln- Prozeduren verwendet
werden, dafür aber mit jedem beliebigen Typ, der auf dem Bildschirm
mit diesen beiden Prozeduren ausgegeben werden darf.