Kapitel 5: Tastatureingabe

Inhaltsverzeichnis

Kapitel 7: Benutzerdefinierte Datentypen (UDTs)

6. Variablen und Konstanten

In Kapitel 4 wurden ja bereits die ersten Variablen eingeführt. Eine Variable ist, wie schon erwähnt, eine Speicherstelle, an der Werte abgelegt werden können. Welche Art von Werten eine Variable speichern kann, hängt vom Variablentyp ab.

Grundsätzlich gibt es in FreeBASIC vier verschiedene Typen von Variablen:

  • Ganzzahlen (also ohne Nachkommastellen)

  • Gleitkommazahlen (Zahlen mit Nachkommastellen)

  • Zeichenketten (Strings)

  • Wahrheitswerte

Außerdem ist es möglich aus den vorgegebenen Datentypen eigene benutzerdefinierte zusammenzubauen — eine Sache, die in Kapitel 7 beschrieben wird.

6.1 Ganzzahlen

6.1.1 Verfügbare Ganzzahl-Datentypen

Die Ganzzahl-Typen in FreeBASIC unterscheiden sich in ihrer Größe, d. h. sowohl im verfügbaren Zahlenbereich als auch im Speicherbedarf. Der kleinstmögliche Typ ist BYTE, welcher — der Name verrät es bereits — ein Byte Speicherplatz benötigt. Ein Byte besteht aus acht Bit, daher kann ein Zahlenbereich von 28=256 Werten gespeichert werden. Dieser muss noch so gleichmäßig wie möglich auf die negativen und positiven Zahlen aufgeteilt werden. Letztendlich ergibt sich ein Zahlenbereich von -128 bis 127.

Der nächstgrößere Datentyp, das SHORT, hat bereits zwei Byte, also 16 Bit, zur Verfügung und bietet damit Platz für 216 = 32 768 Werte — wieder zur Hälfte negativ. Der Datentyp LONG schließlich verbraucht vier Byte Speicherplatz (232 Werte). Wem das noch nicht ausreicht, kann sich mit dem acht Byte großen LONGINT einen Bereich von 264 Werten sichern.

Etwas komplizierter ist die Speicherplatzangabe beim Datentyp INTEGER: dessen Größe hängt davon ab, für welche Plattform compiliert wird. Wenn Sie die 32-Bit-Version des Compilers verwenden, belegt ein INTEGER 32 Bit (entspricht also einem LONG); in der 64-Bit-Version belegt es 64 Bit (entspricht einem LONGINT). Ein INTEGER belegt also immer den Speicherplatz, der von der Architektur direkt in einem Schritt angesprochen werden kann.

Da nicht immer negative Wertebereiche benötigt werden, gibt es zu jedem der vorgestellten Datentypen noch eine vorzeichenlose Variante. „Vorzeichenlos“ heißt im englischen „unsigned“, weshalb zur Kennzeichnung der vorzeichenlosen Datentypen ein U am Anfang des Schlüsselwortes verwendet wird. Ein UBYTE ist ebenso wie ein BYTE acht Bit groß, muss die 256 möglichen Werte jedoch nicht auf den positiven und negativen Bereich aufteilen — daher kann ein UBYTE Zahlen von 0 bis 255 speichern. Entsprechend gibt es auch die Datentypen USHORT, ULONG, ULONGINT und UINTEGER.

Zusammengefasst gibt es also folgende Ganzzahl-Typen:

Datentyp Größe in Bit Grenzen Suffix

BYTE

8 vorzeichenbehaftet

-128 bis +127

 

UBYTE

8 vorzeichenlos

0 bis +255

 

SHORT

16 vorzeichenbehaftet

-32 768 bis +32 767

 

USHORT

16 vorzeichenlos

0 bis +65 535

 

LONG

32 vorzeichenbehaftet

-2 147 483 648 bis +2 147 483 647

&, l

ULONG

32 vorzeichenlos

0 bis +4 294 967 295

ul

LONGINT

64 vorzeichenbehaftet

-9 223 372 036 854 775 808 bis +9 223 372 036 854 775 807

ll

ULONGINT

64 vorzeichenlos

0 bis +18 446 744 073 709 551 615

ull

INTEGER

32/64 vorzeichenbehaftet

siehe LONG bzw. LONGINT

%

UINTEGER

32/64 vorzeichenlos

siehe ULONG bzw. ULONGINT

 

Tabelle 6.1: Datentypen für Ganzzahlen

Das angegebene Suffix kann bei den Zahlendatentypen an die Zahl (nicht an den Variablennamen!) angehängt werden, um für den Compiler deutlich zu machen, dass es sich um den gewünschten Datentyp handelt. Interessant ist das vor allem dann, wenn der Datentyp aus dem Wert heraus automatisch ermittelt wird (z. B. bei CONST oder VAR).

Note Achtung:
Die Variablennamen dürfen, im Gegensatz zu QuickBASIC und vielen anderen BASIC-Dialekten, keine Suffixe erhalten. Alle Variablen müssen explizit über DIM o. ä. deklariert werden.

Bei der Entscheidung, welcher Datentyp nun der beste ist, spielen hauptsächlich zwei Faktoren eine Rolle: der benötigte Speicherplatz und die Verarbeitungsgeschwindigkeit. Dabei ist die Geschwindigkeit häufig das wichtigere Kriterium. Dass die Datentypen nun unterschiedlich viel Speicherplatz benötigen, ist offensichtlich — aber warum erfolgt ihre Verarbeitung unterschiedlich schnell?

FreeBASIC erstellt, je nach Architektur, 32-Bit- bzw. 64-Bit-Programme, das bedeutet, dass 32 bzw. 64 Bit gleichzeitig (innerhalb eines Taktes) verarbeitet werden können. Umgekehrt heißt das aber auch, dass Variablen anderer Größe nicht direkt verwendet werden können. Wenn Sie z. B. in einer 32-Bit-Umgebung eine BYTE-Variable einsetzen, belegt diese nur acht Bit einer 32-Bit-Speicherstelle.1 . Um den Wert der Variablen zu ändern, müssen diese acht Bit zuerst „extrahiert“ und am Ende wieder so zurückgeschrieben werden, dass die restlichen 24 Bit davon nicht betroffen werden. Das geschieht alles automatisch im Hintergrund, und Sie werden von den Operationen nichts mitbekommen — aber dennoch kosten sie ein wenig Zeit. Gerade wenn Sie sehr aufwändige Berechnungen durchführen müssen, kann das durchaus eine Geschwindigkeitseinbuße von bis zu 10% ausmachen! Sofern also der Speicherplatzbedarf keine allzu große Rolle spielt, bietet sich die Verwendung von INTEGER bzw. UINTEGER besser an.

Einen Nachteil dieser flexiblen Anpassung sollte man allerdings beachten: Wenn Sie INTEGER-Variablen in einer Datei speichern und später wieder auslesen, wird das nur dann fehlerfrei funktionieren, wenn das schreibende und das lesende Programm dieselbe INTEGER-Größe verwenden. Insbesondere der Datenaustausch zwischen verschiedenen Architekturen wird dadurch erschwert. Das ist bei weitem keine unüberwindliche Hürde, sollte allerdings im Hinterkopf behalten werden. Am besten verzichten Sie immer dann auf INTEGER bzw. UINTEGER, wenn Sie eine (wie auch immer geartete) feste Größe des Datentyps erwarten. Auf die besonders kritischen Operationen werden Sie im Laufe des Buches ausdrücklich hingewiesen.

6.1.2 Alternative Schreibweise: INTEGER<n>

Die hohe Zahl an Schlüsselwörtern — wie wir gesehen haben, gibt es allein für Ganzzahl-Datentypen bereits zehn Schlüsselwörter — ist nicht unbedingt ein Vorteil der BASIC-Sprachfamilie. Für die Datentypen LONG und INTEGER kommt erschwerend hinzu, dass diese in unterschiedlichen Sprachen (und sogar plattformabhängig) unterschiedlich behandelt werden. Während (sprachübergreifend) in einem 32-Bit-System sowohl LONG als auch INTEGER weitestgehend einheitlich als 32-Bit-Datentyp behandelt werden, bleibt im 64-Bit-FreeBASIC die Länge von LONG gleich, während sich INTEGER-Länge verdoppelt. In C ist das Verhalten compilerabhängig, in der Regel arbeiten aber C-Compiler unter Windows immer mit einer long-Größe von 32 Bit, während unter einem 64-Bit-Linux 64 Bit belegt werden (nicht jedoch unter einem 32-Bit-Linux). Java wiederum verwendet plattformunabhängig immer 32 Bit für den Datentyp int und 64 Bit für den Datentyp long. Wie Sie sehen, gibt es in der Computerwelt keine einheitliche Bezeichnung. Gerade wenn man in mehreren Programmiersprachen gleichzeitig aktiv ist, kann das durchaus Verwirrung stiften.

Um das „Chaos“ etwas zu entschärfen, wurde in FreeBASIC mit der Compilerversion v0.90 eine weitere mögliche Schreibweise eingeführt: Sie können nun Ganzzahlen immer mit INTEGER bzw. UINTEGER kennzeichnen, gefolgt von der gewünschten Größe in Spitzklammern. Kurz gesagt bedeutet das:

  • [U]INTEGER<8> ist gleichbedeutend mit [U]BYTE.

  • [U]INTEGER<16> ist gleichbedeutend mit [U]SHORT.

  • [U]INTEGER<32> ist gleichbedeutend mit [U]LONG.

  • [U]INTEGER<64> ist gleichbedeutend mit [U]LONGINT.

Andere Zahlen als 8, 16, 32 und 64 können nicht verwendet werden.

6.1.3 Rechnen mit Ganzzahlen

In FreeBASIC stehen die gewohnten Grundrechenarten und eine große Zahl mathematischer Funktionen zur Verfügung. Der Compiler folgt dabei den gewohnten Rechenregeln wie Punkt vor Strich und Vorrang der Klammern. Syntaktisch sind jedoch, bedingt durch den zur Verfügung stehenden Zeichensatz, einige Besonderheiten zu beachten. U. a. können für die Berechnung nur runde Klammern verwendet werden (eckige und geschweifte Klammern haben syntaktisch eine andere Bedeutung), diese können aber beliebig oft ineinander verschachtelt werden. Auch Exponenten ab und der Malpunkt im Produkt c·d müssen auf eine andere Art geschrieben werden. Tabelle 6.2 fasst die Rechenzeichen noch einmal zusammen.

a + b

Addition

a - b

Subtraktion

a * b

Multiplikation

a / b

Division

a \ b

Integerdivision (ohne Rest)

a ^ b

Potenz ab

Tabelle 6.2: Grundrechenarten

Da alle Datentypen nur einen eingeschränkten Wertebereich besitzen, kann es vorkommen, dass das Ergebnis einer Rechnung nicht mehr im erlaubten Bereich liegt. Wird auf diese Weise „über den Speicherbereich hinaus“ gerechnet, dann meldet das Programm keinen Fehler, sondern rechnet „auf der anderen Seite“ des Bereichs weiter. Wird z. B. zu einem BYTE mit dem Wert 127 noch 1 hinzugezählt, dann springt der Wert hinunter auf -128. In die andere Richtung gilt dasselbe.

Quelltext 6.1: Rechnen über den Speicherbereich hinaus
DIM AS BYTE start = 100, neu
neu = start + 27
PRINT "100 + 27  = "; neu
neu = start + 28
PRINT "100 + 28  = "; neu
neu = start + 30
PRINT "100 + 30  = "; neu
neu = start - 300
PRINT "100 - 300 = "; neu
SLEEP
Ausgabe:
100 + 27   =  127
100 + 28   = -128
100 + 30   = -126
100 - 300  =  56

Im oben stehenden Beispiel wurde nur der Datentyp BYTE verwendet, da dies der kleinste Datentyp ist und sich bei ihm das Verhalten am einfachsten nachvollziehen lässt. Prinzipiell ist das Verhalten jedoch bei allen Ganzzahl-Datentypen gleich: Sobald die Grenze des Speicherbereichs über- oder unterschritten wird, wird „auf der anderen Seite“ des Bereichs weitergerechnet. Dieses Verhalten (man nennt es auch Überlauf, engl.: overflow) kann durchaus erwünscht sein,2 manchmal aber auch zu unerwarteten Fehlern führen. Sie sollten sich diese Tatsache daher immer vor Augen halten und ggf. Prüfungen einbauen, ob über den Zahlenbereich hinaus gerechnet wird.

Die Zeichen +, - usw. werden Operatoren genannt (in diesem Fall Rechenoperatoren), die beiden Zahlen links und rechts vom Operator sind die Operanden. Wir werden aber im Laufe dieses Buches noch auf eine ganze Reihe weiterer Operatoren stoßen.

6.1.4 Kurzschreibweisen

Zu den oben genannten Rechenoperatoren gibt es Kurzschreibweisen, welche, auch in diesem Buch, gern genutzt werden. Will man zu einer gegebenen Zahl einen bestimmten Wert addieren, lässt sich das auch in verkürzter Form als zahl += wert schreiben.

n += x    ' ist identisch mit n = n+x
n -= x    ' ist identisch mit n = n-x
n *= x    ' ist identisch mit n = n*x
n /= x    ' ist identisch mit n = n/x
n \= x    ' ist identisch mit n = n\x
n ^= x    ' ist identisch mit n = n^x

Vor allem den Kurzformen n += 1 und n -= 1 werden Sie häufiger begegnen, wenn ein Wert inkrementiert (also um 1 erhöht) oder dekrementiert (um 1 vermindert) werden soll. Die Kurzformen existieren aber nicht nur für die Rechenoperatoren, sondern für nahezu alle Operatoren mit zwei Operanden — Ausnahmen sind offensichtliche Fälle wie Vergleichsoperatoren, bei denen eine Kurzschreibweise keinen Sinn ergibt.

6.2 Gleitkommazahlen

6.2.1 Darstellungsbereich von Gleitkommazahlen

Um mit Gleitkommazahlen zu rechnen, gibt es die beiden Datentypen SINGLE und DOUBLE. Die Schlüsselwörter stehen für single bzw. double precision, also einfache bzw. doppelte Genauigkeit. Ein SINGLE belegt vier Byte, während ein DOUBLE mit acht Byte den doppelten Speicherplatz belegt und daher die Werte auch genauer speichern kann.

Das Problem ist nämlich folgendes: Die meisten Dezimalzahlen lassen sich gar nicht mit einer endlichen Anzahl an Nachkommastellen schreiben. Wenn z. B. eine Rechnung wie „1 geteilt durch 3“ durchgeführt wird, erhält man ein periodisches Ergebnis mit unendlich vielen Nachkommastellen, mathematisch exakt ausgedrückt durch 1/3 = 0.33333333…

Der Computer kann natürlich nicht unendlich viele Nachkommastellen speichern — dazu bräuchte er ja unendlich viel Speicherplatz — und muss daher das Ergebnis irgendwo abschneiden bzw. runden (ein Taschenrechner macht übrigens dasselbe). Wird dann mit den gerundeten Werten weitergerechnet, ergeben sich dadurch möglicherweise weitere Rundungsfehler. Die Anzahl der gespeicherten Nachkommastellen ist dabei ausschlaggebend für die Rechengenauigkeit.

Ein SINGLE kann etwa 7 Stellen, ein DOUBLE etwa 16 Stellen speichern. Es handelt sich dabei nicht notwendigerweise um Nachkommastellen. Hier kommt die Bedeutung des Begriffs Gleitkommazahl zu tragen. Ein SINGLE kann z. B. den Wert 1234567 oder 12.34567 oder 12345670000 speichern — alle drei Mal gibt es sieben signifikante Stellen, nur die Position des Dezimalpunkts unterscheidet sich. Deutlicher wird das in der Exponentialdarstellung (auch als wissenschaftliche Notation bezeichnet): 12345670000 = 1.234567 · 1010 und 123.4567 = 1.234567 · 102. Auf diese Art und Weise können sowohl sehr große als auch sehr kleine Zahlen (nahe 0) gespeichert werden, ohne dass die Anzahl der signifikanten Stellen davon betroffen ist. Wie groß bzw. wie klein die Zahlen sein dürfen, können Sie der Tabelle entnehmen:

Datentyp kleinster Wert (nahe 0) größter Wert (nahe ∞) Suffix

SINGLE

±1.401 298 e-45

±3.402 823 e+38

!, f

DOUBLE

±4.940 656 458 412 465e-324

±1.797 693 134 862 316e+308

#, d

Tabelle 6.3: Datentypen für Gleitkommazahlen

Die Angabe 1.401298e-45 ist die in FreeBASIC (und vielen anderen Programmiersprachen) übliche wissenschaftliche Notation 1.401298·10-45. Das Komma ist also um 45 Stellen nach links verschoben. Oder genauer gesagt der Dezimalpunkt — wie in den meisten Programmiersprachen wird als Dezimaltrennzeichen der Punkt verwendet. Entsprechend bedeutet 3.402823e+38 eine Verschiebung des Dezimalpunktes um 38 Stellen nach rechts. Damit kann eine Zahl bis zu 340 Sextillionen gespeichert werden.3 Für den Normalbedarf sollte das ausreichen.

Wird eine Zahl betragsmäßig kleiner als der erlaubte kleinste Wert, so wird sie als 0 gespeichert. Wird sie größer als der erlaubte größte Wert, wird sie intern als „unendlich groß“ (∞) bzw. „unendlich klein“ (-∞) behandelt, dargestellt als inf bzw. -inf.

Note Hinweis:
Beim Rechnen mit „unendlich großen“ Zahlen wird kein Runtime-Error ausgegeben — mit inf und -inf wird gerechnet, als ob es sich um normale Zahlen handeln würde. Kann der Wert einer Rechnung nicht exakt bestimmt werden, wird stattdessen der Wert nan (oder -nan; steht für „not a number“) verwendet. Das kann z. B. bei den Rechnungen inf-inf oder inf*0 oder bei der Division durch 0 geschehen. Das genaue Ausgabeformat unterscheidet sich dabei je nach System — unter Windows werden Sie möglicherweise die Ausgabe 1.#IND statt nan und 1.#INF statt inf zu lesen bekommen.
nan und inf arbeiten unter FreeBASIC allerdings nicht ganz so, wie von der Norm IEEE 754 vorgesehen. Daher reicht es, von ihrer Existenz zu wissen; ein korrekter Umgang mit den Werten ist mit der Standardbibliothek leider nicht möglich.

6.2.2 Rechengenauigkeit bei Gleitkommazahlen

Während sich die Genauigkeit von Ganzzahlberechnungen exakt angeben lässt, müssen wir bei Berechnungen mit Gleitkommazahlen mit einer gewissen Rechenungenauigkeit zurechtkommen. Das folgende Beispiel zeigt an, wie die Ergebnisse von 1/3, 3/7 und 100/3 gespeichert werden.

Quelltext 6.2: Rechnen mit Gleitkommazahlen
DIM s AS SINGLE, d AS DOUBLE
' Berechnung von 1/3
s = 1 / 3
PRINT "1/3 als SINGLE: "; s
d = 1 / 3
PRINT "1/3 als DOUBLE: "; d

' Berechnung von 3/7
s = 3 / 7
PRINT "3/7 als SINGLE: "; s
d = 3 / 7
PRINT "3/7 als DOUBLE: "; d

' Berechnung von 100/3
s = 100 / 3
PRINT "100/3 als SINGLE: "; s
d = 100 / 3
PRINT "100/3 als DOUBLE: "; d
SLEEP
Ausgabe:
 1/3 als SINGLE:  0.3333333
 1/3 als DOUBLE:  0.3333333333333333
 3/7 als SINGLE:  0.4285714
 3/7 als DOUBLE:  0.4285714285714285
 100/3 als SINGLE:  33.33333
 100/3 als DOUBLE:  33.33333333333334

Die ersten beiden Ausgabezeilen werfen keine weiteren Probleme auf. Gleitkommazahlen haben nur eine gewisse Genauigkeit, daher muss ab einer bestimmten Stelle gerundet werden. Beim SINGLE findet die Rundung an der siebten Nachkommastelle statt, beim DOUBLE nach der sechzehnten. Die vierte Ausgabezeile zeigt jedoch schon eine Unregelmäßigkeit: Die letzte Ziffer wurde offenbar falsch gerundet. In der sechsten Ausgabezeile wird das noch deutlicher: Die letzte Ziffer hätte eine 3 statt einer 4 sein müssen.

Bevor Panik wegen falsch rechnender Computer ausbricht: Der Grund an den Unstimmigkeiten liegt im Unterschied zwischen dem Zahlensystem des Computers und unserem. Der Computer rechnet üblicherweise im Binärsystem, also einem Zahlensystem, das nur die Ziffern 1 und 0 kennt. Damit kann er nur diejenigen Brüche exakt behandeln, welche als Nenner eine Zweierpotenz besitzen (1/2, 1/4, 1/8 usw.). Andere Bruchzahlen werden wie gewohnt gerundet, nur dass die Rundung im Binärsystem etwas anders ausfallen kann und das Ergebnis bei der Übersetzung in unser Dezimalsystem falsch erscheinen mag. Der Fehler passiert also genau genommen nicht bei der Division, sondern bei der Übersetzung des Ergebnisses in das andere Zahlensystem. Bei dieser Übersetzung kann es dann auch passieren, dass einmal eine angezeigte Stelle mehr oder weniger herauskommt.

In den letzten beiden Ausgabezeilen erkennt man auch die eben erwähnte Rechengenauigkeit nach signifikanten Stellen. Da die Zahlen mehr Stellen vor dem Komma besitzen, sinkt die Anzahl der Nachkommastellen. Der Computer speichert lediglich die Ziffernfolge sowie die Position des Dezimalpunktes. Die Ausgabe erfolgt ab einer bestimmten Größe in Exponentialschreibweise:

PRINT ((123 / 1000000) / 1000000) / 1000000
SLEEP

Ausgegeben wird der Wert 1.23e-16. Gemeint ist damit 1.23·10-16, also der Wert 0.000000000000000123, der durch eine Verschiebung des Dezimalpunktes um 16 Stellen nach links entsteht.

Um noch einmal darauf zurückzukommen: auch bei doppelter Rechengenauigkeit müssen Sie eine gewisse Ungenauigkeit in Kauf nehmen. Probieren Sie einmal folgendes Programm aus:

PRINT 0.5 - 0.4 - 0.1
SLEEP

Auf den meisten Plattformen wird man folgende oder eine ähnliche Ausgabe erhalten, die auf den ersten Blick überraschen mag:

Ausgabe:
-2.775557561562891e-17

Das Ergebnis hat eine Größenordnung von 10-17; sein Betrag ist also kleiner als ein Zehnbilliardstel. Dennoch hätten wir das Ergebnis 0 erwartet. Dieser, wenn auch sehr kleine, Unterschied kommt wieder daher, dass Computer nicht wie wir im Dezimalsystem, sondern im Binärsystem rechnen. Im Binärsystem sind die Werte der Dezimalzahlen 0.4 und 0.1 periodisch und werden entsprechend gerundet. Der Rundungsfehler wird meistens nicht groß ins Gewicht fallen, aber je nach Rechnung kann er auch mehr oder weniger starke Auswirkungen haben. Sie werden gegen diese Rechenfehler nichts tun können; seien Sie sich einfach immer darüber im Klaren, dass es absolute Genauigkeit bei Gleitkommazahl-Rechnungen nicht gibt.

Im Buch verwenden wir für Gleitkommazahlen immer DOUBLE (außer es soll etwas Spezielles mit SINGLE-Werten demonstriert werden). Auf heutigen Computerarchitekturen gibt es in der Regel keinen Geschwindigkeitsunterschied zwischen Gleitkommazahlen einfacher und doppelter Genauigkeit (viele Prozessoren arbeiten bei Gleitkommazahlen nicht mit 32 oder 64, sondern mit 80 Bit), weshalb es wenig Gründe gibt, auf die mit DOUBLE verbundene höhere Genauigkeit zu verzichten.

6.3 Zeichenketten

Als dritten Standard-Datentyp stellt FreeBASIC Zeichenketten (Strings) zur Verfügung. Dabei handelt es sich um eine beliebige Aneinanderreihung von Zeichen (Buchstaben, Ziffern, Sonderzeichen); eine Zeichenkette kann aber auch komplett leer sein (ein sogenannter Leerstring). Im Quelltext müssen Strings immer in "Anführungszeichen" stehen. Wir haben solche Zeichenketten bereits in den vorigen Kapiteln kennen gelernt.

6.3.1 Arten von Zeichenketten

Ein STRING besteht also aus einer Aneinanderreihung von erweiterten ASCII-Zeichen. Damit FreeBASIC weiß, wo die Zeichenkette endet, reserviert es intern — vom Benutzer versteckt — eine weitere Speicherstelle für die Länge der Zeichenkette. Dies sieht, stark vereinfacht dargestellt, etwa folgendermaßen aus:

<es folgen 006 Zeichen>WETTER
<es folgen 013 Zeichen>WETTERBERICHT

Daneben gibt es noch zwei weitere Typen: den ZSTRING und den WSTRING.
ZSTRING steht für zero-terminated string, also zu Deutsch eine nullterminierte Zeichenkette. Ein ZSTRING besitzt keine Speicherstelle für die Länge, sondern hängt ein reserviertes „Stoppzeichen“ an, um das Ende der Zeichenkette zu markieren: das Nullbyte mit dem ASCII-Wert 0.

WETTER<Stopp>
WETTERBERICHT<Stopp>

Im ZSTRING darf nun aber kein Nullbyte vorkommen, da es ja als Stoppzeichen interpretiert werden würde. Würde die Zeichenkette folgendermaßen aussehen:

WETTER<Stopp>BERICHT<Stopp>

dann würde sie lediglich als WETTER interpretiert werden und der hintere Teil BERICHT ginge verloren. Ein STRING unterliegt dieser Einschränkung nicht. Bis auf das Nullbyte sind in einem ZSTRING jedoch alle Zeichen erlaubt, die auch in einem STRING verwendet werden können. Welche Zeichen das sind, können Sie Anhang C entnehmen.

Note Achtung:
Für Konsole und Grafikfenster werden verschiedene ANSI-Codepages verwendet (siehe Anhang C).

ZSTRING wird vor allem benötigt, um einen Datenaustausch mit externer Bibliotheken (z. B. solchen, die in C geschrieben wurden) zu ermöglichen. Ein STRING verwendet intern ein eigenes Speicherformat, das nicht ohne weiteres auf andere Programmiersprachen übertragen werden kann. FreeBASIC-intern ist jedoch der Datentyp STRING in der Regel leichter zu handhaben.

Ein WSTRING ist wie ein ZSTRING nullterminiert, nutzt also „Null-Zeichen“ als Stoppzeichen. Er speichert jedoch keine ASCII-, sondern Unicode-Zeichen, besitzt also einen wesentlich größeren Zeichenvorrat. Dafür wird pro Zeichen natürlich auch mehr Speicherplatz benötigt. Da FreeBASIC bei der Unicode-Unterstützung auf die C runtime library zurück greift, die plattformbedingt variiert, gibt es zwischen den verschiedenen Betriebssystem kleine Unterschiede: Unter DOS wird Unicode nicht unterstützt. Unter Windows werden WSTRINGs in UCS-2 codiert (ein Zeichen belegt 2 Bytes), während sie unter Linux in UCS-4 codiert werden (ein Zeichen belegt 4 Bytes). Entsprechend besteht natürlich auch das terminierende „Null-Zeichen“ nicht aus einem, sondern aus zwei bzw. vier Byte.

Im Augenblick werden wir uns auf die Verwendung von STRINGs beschränken. Auf den Umgang mit ZSTRING und WSTRING wird in Kapitel 15 genauer eingegangen; dort erfahren Sie auch Näheres über den internen Aufbau eines STRINGs und darüber, was es mit Zeichenketten fester Länge auf sich hat.

Ein STRING kann in der 32-Bit-Version des Compilers bis zu 2 GB groß werden, in der 64-Bit-Version sogar 8 388 607 TB (die Größe entspricht also dem plattformabhängigen Wertebereich eines INTEGERs).

Tip Hintergrundinformation:
Auch beim Datentyp STRING wird intern am Ende ein Nullbyte angehängt, um kompatibler zu externen Bibliotheken zu sein. Dieses Nullbyte ist jedoch innerhalb von FreeBASIC nicht terminierend.
Achtung: In QuickBASIC wird kein solches Nullbyte angehängt. Insbesondere sind UDTs, die STRINGs enthalten, zwischen QuickBASIC und FreeBASIC nicht kompatibel!

6.3.2 Aneinanderhängen zweier Zeichenketten

Selbstverständlich kann mit Zeichenketten nicht „gerechnet“ werden — dies bleibt den Zahlen vorbehalten — jedoch gibt es auch für sie Bearbeitungsmethoden. Auch für Zeichenketten ist das Pluszeichen definiert, besitzt dort jedoch eine etwas andere Bedeutung: mit ihm werden zwei Zeichenketten einfach aneinander gehängt. Man spricht dabei von einer Stringverkettung oder Konkatenation der Zeichenketten.

Quelltext 6.3: Stringverkettung mit Pluszeichen
DIM AS STRING vorname = "Max", nachname = "Muster", gesamtname
gesamtname = vorname + " " + nachname
PRINT gesamtname

PRINT  123  +  456
PRINT "123" + "456"

SLEEP
Ausgabe:
Max Muster
 579
123456

Zunächst einmal wurde der Vorname mit einem Leerzeichen und dem Nachnamen zusammengehängt. Das Ergebnis ist wohl sehr einleuchtend. Dasselbe passiert auch, wenn die Zeichenketten Zahlenzeichen enthalten — sie werden einfach aneinander gehängt. Dem Compiler ist dabei egal, was die Zeichenketten enthalten; er behandelt Ziffern in einer Zeichenkette genauso wie Buchstaben oder Sonderzeichen. Während in Zeile 5 eine normale Addition zweier Zahlen durchgeführt wird, handelt es sich in Zeile 6 um zwei Zeichenketten, die aneinandergehängt werden. Das Pluszeichen erzielt also für die verschiedenen Datentypen zwei völlig unterschiedliche Ergebnisse.

Eine „Addition“ einer Zahl mit einer Zeichenkette ist nicht möglich und wird zu einem Compiler-Fehler führen. Stattdessen muss zuerst die Zahl in einen String umgewandelt werden, um beide Zeichenketten miteinander zu verketten, oder der String in eine Zahl, um eine Rechenoperation durchzuführen. Wie eine solche Umwandlung durchgeführt wird, folgt später. Für eine String-Konkatenation gibt es jedoch noch eine andere Möglichkeit: die et-Ligatur & (auch kaufmännisches Und oder Ampersand genannt). Der Vorteil bei & ist, dass die verwendeten Werte automatisch zu Zeichenketten umgewandelt werden. Sie können es also auch zur Verkettung eines Strings mit einer Zahl oder sogar zur Verkettung zweier Zahlen verwenden — diese werden zuerst zu Zeichenketten umgewandelt und anschließend aneinandergehängt.

Quelltext 6.4: Stringverkettung mit et-Ligatur
DIM AS STRING vorname = "Max", nachname = "Muster", gesamtname
gesamtname = vorname & " " & nachname
' arbeitet genauso wie vorname + " " + nachname
PRINT gesamtname

PRINT  123  +  456
PRINT "123" + "456"
PRINT  123  &  456
PRINT "ABC" &  456
' PRINT "ABC" + 456   ' FEHLER: Das ist nicht erlaubt!

SLEEP
Ausgabe:
Max Muster
 579
123456
123456
ABC456

Natürlich kann man mit Zeichenketten noch allerhand andere interessante Dinge anstellen, wie z. B. nur einen Teil der Zeichenkette ausgeben, in Groß- oder Kleinbuchstaben umwandeln und vieles mehr. Dazu erfahren Sie mehr in Kapitel 15.

6.4 Wahrheitswerte

Als letzten Standarddatentyp steht das Boolean zur Verfügung (benannt nach dem englischen Mathematiker George Boole). Ein BOOLEAN kann nur zwei Werte annehmen: den Wert true (wahr) und den Wert false (falsch).

DIM AS BOOLEAN wahrheitswert = true
PRINT wahrheitswert
SLEEP

Die Bedeutung des BOOLEAN wird erst ab Kapitel 10 wirklich interessant. Allerdings sollte bereits jetzt erwähnt werden, dass FreeBASIC auch alle Zahlenwerte als Wahrheitswerte interpretiert: Ist die Zahl 0, so gilt sie als falsch; in allen anderen Fällen gilt sie als wahr. Die Trennungslinie zwischen BOOLEAN und Zahlen läuft daher nicht immer so sauber, wie es zu wünschen wäre; wenn sie aber zu grob missachtet wird, erhalten Sie zumindest eine Warnung vom Compiler.

6.5 Konstanten

Konstanten sind den Variablen sehr ähnlich, allerdings kann ihnen nur bei der Deklaration ein Wert zugewiesen werden. Eine spätere Änderung des Wertes ist nicht möglich. Konstanten können z. B. eingesetzt werden, wenn zu Beginn des Programmes bestimmte Einstellungen festgelegt werden sollen, die sich später nicht mehr ändern dürfen. Eine weitere Möglichkeit ist die Definition mathematischer oder physikalischer Konstanten, die im Programm benötigt werden. Konstanten werden üblicherweise komplett in Großbuchstaben geschrieben, um sie sofort als solche erkennen zu können (auch wenn dem Compiler die Groß- und Kleinschreibung nach wie vor egal ist).

Außerdem sind Konstanten im ganzen Programm gültig — eine Eigenschaft, die z. B. beim Einsatz von Prozeduren interessant wird (vgl. Kapitel 12).

Das folgende Programm legt den Programmnamen und die Kreiszahl π als Konstanten fest. Die Werte können anschließend genauso wie Variablen ausgegeben oder für Berechnungen, bei STRINGs auch für Konkatenationen verwendet werden.

Quelltext 6.5: Konstanten
CONST TITEL = "Kreisberechnungsprogramm"
CONST PI = 3.14    ' zwar recht ungenau, reicht aber fuer unsere Zwecke
DIM AS INTEGER radius
 
PRINT TITEL
PRINT
INPUT "Gib den Kreisradius an: ", radius
PRINT "Der Kreis hat etwa den Umfang " & (2*radius*PI);
PRINT " und den Flaecheninhalt " & (radius^2*PI)
SLEEP
Ausgabe:
Kreisberechnungsprogramm

Gib den Kreisradius an: 12
Der Kreis hat etwa den Umfang 75.36 und den Flaecheninhalt 452.16

Wie Sie sehen, muss bei der Anweisung CONST nicht angegeben werden, um welchen Datentypen es sich handelt. FreeBASIC entscheidet selbständig, welcher Datentyp für die angegebenen Daten am praktischsten ist. Ganzzahlen werden als INTEGER gespeichert (wegen der Rechengeschwindigkeit) oder (in 32-Bit-Systemen) als LONGINT, wenn ein INTEGER zu klein für den angegebenen Wert ist. Gleitkommazahlen werden als DOUBLE gespeichert (wegen der Rechengenauigkeit). Ob es sich bei dem Wert um eine Ganzzahl oder eine Gleitkommazahl handelt, wird ganz einfach daran unterschieden, ob ein Dezimalpunkt vorkommt: 1 ist eine Ganzzahl und 1.0 eine Gleitkommazahl.

Allerdings kann der Datentyp einer Konstanten auch direkt festgelegt werden:

CONST PI AS SINGLE = 3.14

Damit wird PI nicht mehr als DOUBLE, sondern als SINGLE verarbeitet.

Auch die Verwendung der in Tabelle 6.1 angeführten Suffixes ist erlaubt, z. B.:

CONST x = 45ul
CONST y = 45%
CONST z = 45LL   ' Auch bei Suffixen wird die Gross-/Kleinschreibung ignoriert

6.6 Nummerierungen (Enumerations)

Eine besondere Art der Konstanten sind die Enumerations. Hierbei handelt es sich um eine einfache Nummerierung von Konstanten-Werten: Sie geben lediglich eine Folge von Namen für die gewünschten Konstanten an, und der Compiler übernimmt automatisch die Wertzuweisung. Als Datentyp wird sinnvollerweise immer INTEGER verwendet.

Eine Enumeration bietet sich an, wenn Sie verschiedene unterscheidbare Werte mit sprechenden Namen belegen wollen, der tatsächliche Wert dabei aber unerheblich ist. Denken Sie dabei z. B. an verschiedene Optionen in einem Auswahldialog:

Quelltext 6.6: Verwendung von ENUM
ENUM AUSWAHL
  einkauf, verkauf, ausbau, preisaenderung
END ENUM

DIM AS AUSWAHL meineWahl = verkauf
'...

Die Verwendung eines Listennamen (hier AUSWAHL) ist optional, aber empfehlenswert. Unter anderem können Sie dann wie in Quelltext 6.6 diese Enumeration auch als Typ einer Variablen verwenden (der Compiler überprüft aber leider nicht, ob anschließende Wertzuweisungen wirklich Werte aus der Enumeration sind). Außerdem kann der Name vor die Variable gesetzt werden, um die Zugehörigleit eindeutig festzulegen:

meineWahl = AUSWAHL.verkauf

Das bringt einige Vorteile mit sich — der entscheidenste ist sicherlich, dass verschiedene Enumerations dieselben Variablennamen verwenden können, ohne sich gegenseitig in die Quere zu kommen, und dass Sie bei der Namenswahl „normaler“ Variablen weniger eingeschränkt werden. Immerhin kann es in größeren Projekten leicht vorkommen, dass Sie verschiedene externe Bibliotheken verwenden, welche dieselben Namen definieren wollen. Solange nun die Enumerations unterschiedlich benannt sind, stören gleiche Variablennamen nicht. (Über Bereichsnamen, die in [KapGueltigkeitNamespace] behandelt werden, können auch Konflikte gleichnamiger Enumerations verhindert werden.)

Wenn Sie die Verwendung des Listennamens erzwingen wollen, geben Sie dahinter das Schlüsselwort EXPLICIT an:

ENUM AUSWAHL EXPLICIT
  einkauf, verkauf, ausbau, preisaenderung
END ENUM

' DIM AS AUSWAHL meineWahl = verkauf        ' funktioniert nicht mehr
DIM AS AUSWAHL meineWahl = AUSWAHL.verkauf  ' funktioniert dagegen
'...

Sie sehen, dass Sie dadurch für mehr Schreibarbeit sorgen; dafür wird aber auch die Lesbarkeit deutlich erhöht, da jederzeit klar ist, woher der Wert einkauf stammt.

Ein umfangreiches Beispiel mit der Verwendung von ENUM finden Sie in Quelltext 14.14.

Noch eine Information zur Wertzuweisung:
FreeBASIC weist innerhalb der Enumeration der ersten Variablen den Wert 0 zu und zählt dann jeweils um 1 nach oben. In Quelltext 6.6 ist dann also AUSWAHL.einkauf=0, AUSWAHL.verkauf=1, AUSWAHL.ausbau=2 und AUSWAHL.preisaenderung=4. Dieses Verhalten können Sie auch beeinflussen, indem Sie einer Variablen den gewünschten Wert zuweisen. Alle folgenden Variablen werden anschließend normal weiternummeriert.

Quelltext 6.7: ENUM mit Wertzuweisung
ENUM AUSWAHL
  einkauf, verkauf, ausbau=7, preisaenderung
END ENUM

PRINT AUSWAHL.einkauf, AUSWAHL.verkauf, AUSWAHL.ausbau, AUSWAHL.preisaenderung 
SLEEP
Ausgabe:
 0             1             7             8

Enumerations sind jedoch weiterhin Konstanten. Eine Wertzuweisung ist ausschließlich bei der Definition zulässig, später ist keine Änderung mehr möglich.

6.7 Weitere Speicherstrukturen

Neben den bisher behandelten Standard-Datentypen (also Ganzzahlen, Gleitkommazahlen und Zeichenketten) stehen noch weitere Möglichkeiten der Datenspeicherung zur Verfügung, die aber in gesonderten Kapiteln behandelt werden. Zum einen lassen sich eigene Datentypen definieren, die sich aus den vorhandenen zusammensetzen, zum anderen gibt es die Arrays, die eine ganze Gruppe von Daten gleichzeitig beinhalten.

6.8 Fragen zum Kapitel

  1. Welcher Datentyp bietet sich an, um eine Ganzzahl in der Größenordnung von ±1 000 000 000 zu speichern?

  2. Welcher Datentyp bietet sich an, um eine positive Ganzzahl bis 255 zu speichern?

  3. Welche Datentypen können für Gleitkommazahlen verwendet werden? Welche Unterschiede gibt es zwischen ihnen?

  4. Worin liegt der Unterschied zwischen einem STRING und einem ZSTRING?

  5. Was ist der Unterschied zwischen einer Variablen und einer Konstanten?

  6. Was ist der Unterschied zwischen LONG und INTEGER?


Fußnoten:
1) Nichtsdestotrotz wird das System oft eine komplette INTEGER-Stelle reservieren, d. h. bei einzelnen, nicht in größeren Strukturen eingebundenen BYTE kommen Sie unter Umständen gar nicht in den Genuss der Speicherersparnis, müssen aber trotzdem mit dem Geschwindigkeitsverlust leben.

2) Der Versuch vieler Staatsregierungen, durch ausreichend hohe Verschuldung einen Overflow zu erreichen, sind leider zum Scheitern verurteilt.

3) Beachten Sie hierzu aber, dass die Genauigkeit eines DOUBLE weiterhin bei etwa 16 Stellen liegt. Wenn Sie zu 340 Sextillionen eine weitere Milliarde addieren, bleiben Sie nach wie vor bei 340 Sextillionen — der Summand ist hier zu klein, um eine Veränderung herbeizuführen.


Kapitel 5: Tastatureingabe

Inhaltsverzeichnis

Kapitel 7: Benutzerdefinierte Datentypen (UDTs)