1 Standard-Datentypen
1.1 Variablen-Deklaration - wieso denn das?
Pascal gehört (wie C übrigens auch!) zu den "typ-strengen" Sprache, d.h.: jede Variable muss vor ihrer ersten Verwendung im Programm "deklariert" werden. Dies ist durchaus nicht in allen Programmiersprachen so, und manche Hobby-Programmierer sehnen sich beim ersten Kontakt mit einer typ-strengen Sprache nach dem Komfort vieler "Basic"-Dialekte zurück, bei denen man eine Variable im Programm verwenden kann, ohne dies in irgend einer Weise vorbereitet zu haben. Wozu ist dieser Aufwand in den "ernsthaften" Programmiersprachen eigentlich nötig?
Wenn Variablen im Speicher abgelegt werden, geschieht dies so, dass an die zugehörige Stelle im ein bestimmtes Bitmuster geschrieben wird, das den Variableninhalt darstellt. Der Typ der Variablen gibt Auskunft über das Format, d.h. darüber, wie dieses Bitmuster zu interpretieren ist, was es also "bedeutet"! Programmiersprachen, wie auf Deklarationen verzichten, machen zur Laufzeit eigene Annahmen über den Typ der verwendeten Variablen. Und die werden nicht immer genau mit den Absichten des Programmierers übereinstimmen, was zu schwierig zu erkennenden Fehlern führen kann. In den typ-strengen Sprachen hingegen muss der Programmierer genau spezifizieren, was er will - und Klarheit ist immer ein Vorteil.
Betrachten wir ein konkretes Beispiel: die Deklaration
var n : Integer;
erledigt zwei Aufgaben:
- sie weist den Compiler an, in dem Programm dafür zu sorgen, dass zur Laufzeit für eine im Programmtext n genannte Variable Speicherplatz reserviert wird;
- der Typ "Integer" der Variablen enthält er die Information, in welchem Format die Variable im Speicher steht, also wieviele Bytes Speicherplatz die Variable benötigt und was die einzelnen Bits bedeuten.
Liest ein Delphi-Compiler (der Version 2 oder höher) die obige Deklaration, dann wird er 4 Byte Speicherplatz für die Variable n reservieren und dafür sorgen, dass die Bytes in umgekehrter Reihenfolge(!) ihrer Wertigkeit in den Speicher geschrieben werden. Nach der Zuweisung
n := 137682;
stehen also (wegen 137682 = 219D2h) die Bytes
D2h 19h 02h 00h
hintereinander an der für n reservierten Stelle des Speichers.
Selbst wenn die Variable nur 1 Byte groß ist, ist die Typangabe nötig und sinnvoll: die Datentypen "Byte" und "Char" sind von dieser Art. Es ist aber durchaus ein Unterschied, ob der Speicherinhalt "4Dh" die (Byte-)Zahl "77" oder den Buchstaben "M" bedeuten soll!
1.2 Bisher bekannte Datentypen
In Delphi (ab Version 2) sind schon eine ganze Menge von Standard-Datentypen "von Haus aus" verfügbar. Die für uns wichtigsten sind:
| Variablenart |
Typ-Bezeichner |
Werte-Bereich |
Belegter Speicher |
| Ganze Zahlen |
Byte |
0 .. 255 |
1 Byte |
| Integer |
–2 147 483 648 .. 2 147 483 647 |
4 Byte |
| Fließkomma-Zahlen |
Single |
1.5 x 10^–45 .. 3.4 x 10^38, Genauigkeit 7-8 Dezimalstellen |
4 Byte |
| Double |
5.0 x 10^–324 .. 1.7 x 10^308, Genauigkeit 15-16 Dezimalstellen |
8 Byte |
| Zeichen |
Char |
{die 256 Zeichen der ASCII-Tabelle} |
1 Byte |
| Zeichenketten |
String |
{n Zeichen, n <= 255} |
(n+1) Byte |
Diese Tabelle ist bei weitem nicht vollständig; es sind nur die für uns bisher wichtigsten Datentypen aufgeführt. Die Angaben zum belegten Speicher können bei den verschiedenen Delphi-Versionen von den obigen Angaben abweichen, speziell bei den Zeichenketten, deren Implementierung in den letzten Jahren mehrfach überarbeitet wurde. Wer's genauer wissen will, kann Details dazu in der Online-Hilfe zu Delphi finden (Stichwort "Typen").
1.3 Der kleinste mögliche Datentyp: Boolean
In der obigen Tabelle fehlt noch ein Datentyp, der bisher noch nicht explizit aufgetaucht ist, den wir aber sehr wohl schon verwendet haben. Es handelt sich dabei um den Datentyp Boolean, der nur die beiden "Wahrheitswerte" true und false (also wahr und falsch) annehmen kann. Obwohl die damit transportierte Informationsmenge naturgemäß nur 1 Bit groß ist, belegt eine Variable von diesem Typ im Speicher ein ganzes Byte! Dies liegt daran, dass aus technischen Gründen auf den Speicher stets (mindestens) byte-weise zugegriffen wird.
Und wo sollen diese seltsamen Variablen in unseren bisherigen Programmen schon vorgekommen sein? Indirekt überall da, wo eine Bedingung programmiert wurde! Also zum Beispiel in der Entscheidungsbedingung jeder IF-ELSE-Konstruktion und in der Abbruchbedingung jeder REPEAT-Schleife! Allerdings haben wir an diesen Stellen bisher nie einzelne Variablen benutzt, sondern stets mit Hilfe von Termen formulierte Aussagen, die sich dann zur Laufzeit als wahr oder falsch erweisen konnten - in Abhängigkeit von den Werten der in den Termen verwendeten Variablen. Die Bedingungen waren also stets "boolsche Ausdrücke"; wenn sie aber zur Laufzeit "ausgerechnet" wurden, dann konnte es stets nur 2 mögliche Ergebnisse geben: wahr oder falsch!
Das Ergebnis der Berechnung eines boolschen Ausdrucks kann nun auch einer boolschen Variablen zugewiesen werden - genau wie das Ergebnis einer numerischen Berechnung in einer Double-Variablen gespeichert werden kann. Man wird boolsche Variablen vor allem in solchen Fällen verwenden, wo der berechnete "Wahrheitswert" an mehreren Stellen des Programms benötigt wird. Das prinzipielle Vorgehen zeigt das folgende
Beispiel:
In einer WHILE-Schleife sollen die Quadrate der natürlichen Zahlen von 1 bis 10 zusammengezählt werden.
| Bisher haben wir dies so programmiert: |
Mit Hilfe der boolschen Variablen "not_yet_ready" könnte man das auch auf folgende Weise implementieren: |
var i, s : Integer;
begin
s := 0;
i := 1;
While i <= 10 do begin
s := s + i*i;
i := i + 1;
end;
end;
|
var i, s : Integer;
not_yet_ready : Boolean;
begin
s := 0;
i := 1;
not_yet_ready := True;
While not_yet_ready do begin
s := s + i*i;
i := i + 1;
not_yet_ready := i <= 10;
end;
end;
|
(Zugegeben, das Beispiel ist künstlich. Es dient lediglich dazu, den Umgang mit boolschen Variablen zu veranschaulichen. Kaum jemand würde die oben gestellte Aufgabe auf die angegebene Art und Weise lösen wollen. Zunächst ist hier eine REPEAT-Schleife sehr viel naheliegender als eine WHILE-Schleife, aber eigentlich ist die Aufgabe ein typischer Fall für eine FOR-Schleife! Und da wir dort keine Bedingung mehr zu verwalten haben, entfällt dann auch die Versuchung, zu einer boolschen Variablen zu greifen.)
Boolsche Variablen tauchen aber auch in den Delphi-Komponenten auf: das klassische Beispiel dafür ist die "Checkbox", die zur Laufzeit ein Häkchen tragen kann oder nicht. Dies wird in der boolschen Variablen "Checkbox.Checked" vermerkt und kann dort jederzeit vom Programm überprüft werden, z.B.:
If CheckBox1.Checked then.....else......;
Man kann mit boolschen Werten auch rechnen: Pascal bietet für die Verknüpfung boolscher Variablen die logischen Operatoren NOT, AND und OR. Für die Priorität dieser Operatoren gilt die Regel "NOT vor AND vor OR" (analog zu "Hoch vor Punkt vor Strich" bei den üblichen mathematischen Operatoren). Damit lassen sich auch kompliziertere zusammengesetzte Bedingungen formulieren, wie z.B.:
If Checkbox1.Checked AND NOT Checkbox2.Checked then....else.....;
oder:
If Checkbox1.Checked OR ((s >= 3) AND (s <= 7)) then.....else.....;
Die äußere Klammer um die beiden Teilbedingungen im letzten Beipiel ist eigentlich überflüssig, da AND ja ohnehin stärker bindet als OR. Häufig setzt man sie trotzdem, damit die Bedingung leichter lesbar wird.
1.4 Typumwandlungen
Gelegentlich muss in einem Programm der Inhalt einer Variablen in verschiedenen "Typ-Ansichten" interpretiert werden. Als Beispiel dafür betrachten wir eine einfache Methode zur Verschlüsselung von Texten, bei der der verschlüsselte Text aus dem Originaltext erzeugt wird, indem die Buchstaben des Alphabets einfach permutiert werden. Die einfachste Variante davon stellt die "Caesar-Verschlüsselung" dar:
Es wird eine kleine natürliche Zahl s (< 26) gewählt.
Der verschlüsselte Text wird erzeugt, indem jeder Buchstabe des Originaltextes durch denjenigen Buchstaben ersetzt wird, der im Alphabet s Stellen weiter hinten steht.
Beispielsweise wird also für s=1 jeder Buchstabe durch seinen Nachfolger im Alphabet ersetzt. Damit das Verfahren am hinteren Ende des Alphabets nicht schief geht, denkt man sich das Alphabet zyklisch geschlossen: nach "Z" kommt wieder "A", dann "B" usw.
Wie wird nun diese Verschlüsselung in einem Programm realisiert? Aus naheliegenden Gründen wollen wir uns auf Texte beschränken, die nur aus Großbuchstaben bestehen. Selbst die Leerzeichen zwischen den einzelnen Worten wollen wir unberücksichtigt lassen, im Vertrauen darauf, dass sich diese nach der Entschlüsselung durch einen hinreichend cleveren Leser aus dem Kontext restaurieren lassen werden.
Nun ordnen wir jedem Buchstaben des Alphabets eine Nummer zu. Solch eine Zuordnung ist ja schon in der ASCII-Tabelle gegeben, wo jedes Zeichen durch seine ASCII-Nummer eindeutig identifiziert ist. Die ASCII-Nummer können wir durch eine Typ-Umwandlung erhalten: sind u und v CHAR-Variablen und a, n, m und b vom Typ INTEGER, dann wird mit
a := Byte(u); {Typ-Umwandlung Zeichen -> Zahl }
die ASCII-Nummer des in u gespeicherten Zeichens in der Variablen a abgelegt. Wenn z.B. u das Zeichen "K" enthält, dann liefert Byte(u) den Wert 75, was die ASCII-Nummer von "K" ist. Wir wollen allerdings (aus später nachvollziehbaren Gründen) für unsere Numerierung bei "A" mit der "0" beginnen. "B" erhält dann die "1", "C" die "2" usw usf. Dies läßt sich erreichen, ohne dass wir die ASCII-Nummer von "A" nachschlagen:
n := a - Byte('A') {Verschiebung ASCII-Nr von 'A' -> 0 }
liefert genau die gewünschte Zuordnung. Die verschlüsselnde Verschiebung um s Zeichen wird nun durch eine schlichte Addition erreicht. Damit wir dabei aber nicht über das Ende des Alphabets hinausrutschen, muss das Ergebnis noch MOD 26 genommen werden:
m := (n + s) MOD 26 {Verschlüsselung: Verschiebung um s Zeichen }
Um nun das zu u passende verschlüsselte Zeichen v zu erhalten, muss dieser Wert für g dann wieder in ein Zeichen umgewandelt werden:
b := m + Byte('A'); {Verschiebung 0 -> ASCII-Nr von 'A' }
v := Char(b); {Typ-Umwandlung Zahl -> Zeichen }
Damit haben Sie alle Bausteine beieinander, die Sie brauchen, um die Caesar-Verschlüsselung zu implementieren.
Aufgaben:
-
Die Caesar-Verschlüsselung:
Schreiben Sie ein Programm "Caesar", das die Eingabe einer kleinen Zahl s und eines einzeiligen Textes gestattet und dann auf Knopfdruck diesen Text in einem zweiten Edit-Feld Caesar-verschlüsselt mit Verschiebung s ausgibt.
Deklarieren Sie dazu eine Funktion "encrypted", die das zu verschlüsselnde Zeichen und die Verschiebung übergeben bekommt und das verschlüsselte Zeichen zurückliefert.
Sie können dasselbe Programm auch zum Entschlüsseln nehmen, indem Sie den verschlüsselten Text ins erste Edit-Feld eintragen und dann ein zur Verschiebung s passendes s* wählen. Wie müssen s und s* zusammenhängen?
Bequemer wäre es allerdings, wenn man den entschlüsselten Text immer in der ersten und den verschlüsselten Text immer in der zweiten Textbox hätte und die Ver- bzw. Entschlüsselung mit jeweils einem eigenen Knopf ausgelöst würde. Schreiben Sie dazu zusätzlich eine Funktion "decrypted", die zu jedem verschlüsselten Zeichen das zugehörige unverschlüsselte zurückliefert, und benutzen Sie diese, um den Text aus der zweiten Edit-Komponente entschlüsselt in der ersten auszugeben.
-
Die Vigenère-Verschlüsselung:
Eine Weiterentwicklung des Caesar-Verfahrens ist die Vigenère-Verschlüsselung. Dabei wird jedes Zeichen des Textes mit einer durch ein Schlüsselwort bestimmten Verschiebung Caesar-verschlüsselt:
- Zunächst wird unter die Nachricht so oft wiederholt das Schlüsselwort geschrieben, bis unter jedem Buchstaben der Nachricht ein Buchstabe des Schlüsselwortes steht.
- Dann wird jeder Buchstabe der Nachricht mit derjenigen Verschiebung Caesar-verschlüsselt, die durch die Position des darunterstehenden Schlüsselbuchstabens im Alphabet bestimmt ist.
(Beispiel: Unter dem Nachrichten-Buchstaben "D" stehe der Schlüsselbuchstabe "K". Dieser führt zu einer Verschiebung um s = 10; das "D" wird also so in ein "N" verschlüsselt.)
Schreiben Sie ein Programm "Vigenere", das die Eingabe eines Schlüsselwortes und eines einzeiligen Textes gestattet und dann auf Knopfdruck diesen Text in einem zweiten Edit-Feld Vigenère-verschlüsselt ausgibt. Implementieren Sie auch die Entschlüsselung!
Damit dieses Programm schön übersichtlich strukturiert wird, sollten Sie zunächst auch hier wieder (genau wie in der vorigen Aufgabe) eine Funktion "encrypted" implementieren, diesmal aber mit 2 Char-Parametern: dem zu verschlüsselnden Buchstaben der Botschaft und dem zugeordneten Schlüsselbuchstaben. Die eigentliche Ver- (und Ent-!) schlüsselung sollte dann durch Aufrufe dieser Funktion erledigt werden.