Kapitel 12: Prozeduren und Funktionen

Inhaltsverzeichnis

Kapitel 14: Verarbeitung von Zahlen

13. Datentypen umwandeln

Die meisten Befehle erfordern für ihre Parameter einen bestimmten Datentyp. Eine Multiplikation z. B. kann nur mit Zahlen durchgeführt werden, nicht aber mit Strings. Wenn Sie eine Zahl als String vorliegen haben und sie mit einer anderen Zahl multiplizieren wollen, müssen Sie sie vorher in einen geeigneten Datentyp umwandeln.

13.1 Allgemeine Umwandlung: CAST()

Der allgemeine Befehl zur Umwandlung von einem Datentyp in einen anderen heißt CAST(). Sie können damit einen Wert in jeden beliebigen Zahlendatentyp oder Pointertyp umwandeln.

neueVariable = CAST(neuerDatentyp, alteVariable)

Als ersten Parameter geben Sie den Datentyp an, in den Sie die (als zweiten Parameter angegebene) Variable umwandeln wollen. Für eine Umwandlung von einem String in eine Ganzzahl kann man dann schreiben:

Quelltext 13.1: Einsatz von CAST
DIM AS STRING zahlString = "246.8"
' Zahlenwert verdoppeln und ausgeben
PRINT 2*CAST(INTEGER, zahlString)
SLEEP
Ausgabe:
492

Es wird Sie sicher nicht verwundern, dass bei einer Änderung von CAST(INTEGER, zahlString) zu CAST(DOUBLE, zahlString) der Nachkomma-Anteil erhalten bleibt. Erwähnenswert ist allerdings, dass bei einer Umwandlung in einen Ganzzahlentyp der Nachkomma-Anteil abgeschnitten, also immer in Richtung 0 gerundet wird.

CAST() löst eine Reihe an Befehlen ab, die ursprünglich von QuickBASIC her kommen und in früheren FreeBASIC-Versionen erweitert wurden, um auch vorzeichenlose Ganzzahlen und (U)LONGINT zu ermöglichen:

Befehl Umwandlung in …    Befehl Umwandlung in …

CBYTE()

BYTE

CUBYTE()

UBYTE

CSHORT()

SHORT

CUSHORT()

USHORT

CINT()

INTEGER

CUINT()

UINTEGER

CLNG()

LONG

CULNG()

ULONG

CLNGINT()

LONGINT

CULNGINT()

ULONGINT

CSIGN()

vorzeichenbehaftete Zahl

CUNSG()

vorzeichenlose Zahl

CSNG()

SINGLE

CDBL()

DOUBLE

CBOOL()

BOOLEAN

CPTR()

Pointer

Tabelle 13.1: Befehle zur Typumwandlung

Allein schon an der puren Fülle der Befehle sehen Sie, warum man in den letzten Jahren von diesen Bezeichnungen abgekommen ist und alles kompakt in den Befehl CAST() gepackt wurde. Die alten Befehle werden aber weiter unterstützt; sie besitzen vor allem den Vorteil, deutlich kürzer zu sein. Aufgrund der Kürze des Befehls werden Sie im Buch auch gelegentlich auf CINT() stoßen; ansonsten empfehle ich eher die Verwendung der „Langform“. Zur Ergänzung: CSIGN() und CUNSG() wandeln die Zahl in die vorzeichenbehaftete bzw. vorzeichenlose Version des gleich großen Datentyps um, also z. B. ein BYTE in ein UBYTE bzw. umgekehrt.

Auch UDTs können mittels CAST in andere Datentypen umgewandelt werden, benötigen dazu aber zuerst die passende Definition einer CAST-Funktion. Dazu erfahren Sie in [KapOopOperator] mehr.

Und dann gibt es da noch …
Ja, dann gibt es da noch VAL() und seine Familie. Ich erwähne die Befehle nur deshalb, weil es leicht sein kann, dass Sie in (vor allem älteren) Quelltexten darauf stoßen. VAL() wandelt einen String in ein DOUBLE um, macht also dasselbe wie CDBL(), außer dass es auf Strings beschränkt ist. Ähnlich ist es mit den anderen Befehlen:

  • VALINT(): Umwandlung in ein LONG (!)

  • VALUINT(): Umwandlung in ein ULONG

  • VALLNG(): Umwandlung in ein LONGINT (!)

  • VALULNG(): Umwandlung in ein ULONGINT

In allen Fällen werden die Nachkommastellen abgeschnitten. Die Funktionen arbeiten also genauso wie die entsprechenden CAST-Funktionen (genauer gesagt: die CAST-Funktionen rufen zur Umwandlung von Strings zuvor eine der Funktionen aus der VALxxx-Familie auf). Es spricht also nichts dagegen, gleich von vornherein CAST() zu verwenden.

13.2 Umwandlung in einen String

Für die Umwandlung einer Zahl in einen String ist CAST() nicht vorgesehen; stattdessen kommt hier STR() zum Einsatz. Liegt der Zahlenwert in einem WSTRING vor, verwenden Sie stattdessen die Variante WSTR().

Quelltext 13.2: Zahl in einen String umwandeln
DIM AS INTEGER alter
INPUT "Gib dein Alter ein: ", alter
PRINT "In einem Jahr bist du " + STR(alter+1) + " Jahre alt."
SLEEP
Ausgabe:
Gib dein Alter ein: 17
In einem Jahr bist du 18 Jahre alt.

Mit dem Operator + können Strings nicht mit Zahlen verkettet werden. Daher ist vorher eine explizite Umwandlung des Zahlenwertes in einen String erforderlich. Natürlich hätten Sie die Werte für die PRINT-Ausgabe auch einfach mit Strichpunkten aneinanderhängen können. Dazu müssen aber zwei Dinge bedacht werden: Zum einen ist eine Aneinanderreihung mit Strichpunkten nur bei PRINT zulässig, nicht aber bei einer Zuweisung in eine Variable oder bei Parametern eines Unterprogrammes oder anderer FreeBASIC-interner Befehle. Zum anderen formatiert STR() etwas anders. Es wird nämlich kein Platzhalter (in Form eines Leerzeichens) für das fehlende positive Vorzeichen gelassen; ein Umstand, der zum Tragen kommt, wenn sowohl positive als auch negative Werte auftauchen können.

DIM AS INTEGER alter
INPUT "Gib dein Alter ein: ", alter
PRINT "mit STR:         In einem Jahr bist du " + STR(alter+1) + " Jahre alt."
PRINT "mit Strichpunkt: In einem Jahr bist du ";  alter+1;       " Jahre alt."
SLEEP
Ausgabe:
Gib dein Alter ein: 17
mit STR:         In einem Jahr bist du 18 Jahre alt.
mit Strichpunkt: In einem Jahr bist du  18 Jahre alt.

Im zweiten Fall wird der positive Wert 18 vorn mit einem Leerzeichen erweitert, wodurch in der Ausgabe ein überflüssiges Leerzeichen steht. Bei negativen Zahlen passt die Ausgabe allerdings:

Ausgabe:
Gib dein Alter ein: -22
mit STR:         In einem Jahr bist du -21 Jahre alt.
mit Strichpunkt: In einem Jahr bist du -21 Jahre alt.

Das bedeutet: In der zweiten Ausgabe einfach ein Leerzeichen wegzulassen, um den Platzhalter bei positiven Zahlen zu kompensieren, ist nicht unbedingt eine passende Lösung.

13.3 Implizite Umwandlung

Während an manchen Stellen eine explizite Umwandlung in den richtigen Datentyp nötig ist, wandelt der Compiler die Daten oft auch automatisch in das passende Format um. Ein Beispiel: Der Operator + hat für Strings und Zahlen vollkommen verschiedene Bedeutung, und der Compiler kann bei einer Vermischung nicht entscheiden, ob der Programmierer nun eigentlich eine String-Addition oder eine Zahl-Addition durchführen wollte. Dagegen können eine Ganzzahl und eine Gleitkommazahl problemlos addiert werden. Zwar läuft auch hier die Addition unterschiedlich ab, was an den vollkommen verschiedenen Speicherformaten beider Zahlentypen liegt, jedoch kann der Compiler davon ausgehen, dass der Programmierer eine Gleitkommaberechnung wünscht. Die Ganzzahl wird daher kurzerhand in eine Gleitkommazahl umgewandelt und dann mit der anderen Zahl addiert.

Diese automatische Umwandlung findet bei Zahlen immer dann (und erst dann) statt, wenn sie benötigt wird. Ganzzahlen werden so lange als Ganzzahlen behandelt, bis ein Rechenschritt eine Gleitkommazahl erfordert. Umgekehrt kann aber auch eine Gleitkommazahl implizit in eine Ganzzahl umgewandelt werden, nämlich dann, wenn als Operanden Ganzzahlen gefordert sind. Das ist z. B. bei der Ganzzahldivision oder bei MOD der Fall.

Ein weiterer Fall der impliziten Umwandlung wurde bereits in Kapitel 6.3.2 angesprochen: die Stringverkettung mit &. Da & nur für Zeichenketten definiert ist, weiß der Compiler, dass gegebenenfalls eine Konvertierung zu einer Zeichenkette durchgeführt werden muss.1 Die explizite Umwandlung mittels STR() kann daher weggelassen werden.

13.4 ASCII-Code

13.4.1 Ursprung und Bedeutung des ASCII-Codes

Computer speichern alle Daten in Speicherzuständen, auch Texte, Bilder usw. Über das Binärsystem können wir diese Zustände mit Zahlenwerten identifizieren. Auch Texte werden gewissermaßen als Zahlenfolgen behandelt. Dazu wird jedem Schriftzeichen ein Zahlenwert zugeordnet. Eine der ältesten Zuordnungstabellen (im Bereich der Computertechnologie) ist der American Standard Code for Information Interchange ASCII, der schnell zu einem weltweiten Standard der Zeichencodierung wurde. Der ASCII-Code verwendet 7 Bit, kann also 128 Zeichen darstellen. Bei den ersten 32 Zeichen handelt es sich um Steuerbefehle, z. B. der Tabulatorvorschub oder das Zeilenende. Die weiteren beinhalten unter anderem die 26 Klein- und ebenso vielen Großbuchstaben des lateinischen Alphabets, die zehn Ziffern 0-9 sowie Leerzeichen und diverse Satzzeichen.

Selbstverständlich ist bei einem Zeichenvorrat von 128 Zeichen nicht genug Platz, um alle länderspezifischen Sonderzeichen wie etwa die deutschen Umlaute mit aufzunehmen. Sprachen, die nicht auf dem lateinischen Alphabet beruhen — als Beispiele seien nur einmal das griechische und das kyrillische Alphabet genannt — benötigen sogar eine beachtliche Menge an zusätzlichen Zeichen. Da sich allerdings schnell die Byte-Größe von 8 Bit durchsetzte und ein ASCII-Zeichen damit nur die Hälfte des Byte-Zahlenbereichs nutzte, lag es nahe, die restlichen 128 Werte mit den Zeichen zu füllen, die man gerade brauchte. Es entstanden eine Reihe an ANSI-Codepages2 mit den national benötigten bzw. gewünschten Zusatzzeichen. Bei uns am bekanntesten sind die Codierungen ISO-8859-1 und ISO-8859-15, die unter anderem die deutschen Umlaute und eine Reihe an Vokalen mit Akzentzeichen enthält und den Zeichenvorrat eines großen Teils der westeuropäischen Sprachen abdeckt. ISO-8859-5 dagegen enthält sämtliche Schriftzeichen des kyrillischen Alphabets, ISO-8859-6 die arabischen Zeichen usw.

Konkret bedeutet das: Wenn Sie einen ANSI-codierten Text vorliegen haben, ohne die konkrete Codepage zu kennen, können Sie die Zeichen ab Nr. 128 nicht korrekt darstellen. Hierin liegt auch der Grund, warum es bei der Verwendung von Umlauten in FreeBASIC zu Problemen kommen kann. Im Gegensatz zum ASCII-Code ist der Begriff ANSI-Code nicht genormt. Und es gibt noch weitere Probleme: In manchen Sprachen gibt es deutlich mehr Schriftzeichen, als in der ANSI-Codierung Platz hätten. Die chinesische Schrift etwa kennt mehrere Tausend Zeichen, selbst wenn die selten verwendeten Zeichen nicht mitgezählt werden. Für eine international geeignete Codierung ist daher ein deutlich größerer Zeichenvorrat nötig. Deshalb wurde Unicode entwickelt, welches heute weit über 100 Schriftsysteme und über 120 000 Zeichen unterstützt.

Wir werden im Folgenden mehrfach auf den Begriff ASCII-Code zu sprechen kommen. Es sei noch einmal darauf hingewiesen, dass diese Codierung lediglich die Zeichen mit den Nummern 0 bis 127 beinhaltet. Der Einfachheit halber werden wir aber auch die Zeichen 128 bis 255 als „erweiterten ASCII-Code“ bezeichnen, um eine ständige Differenzierung zwischen den ASCII-Zeichen und den weiteren ANSI-Zeichen zu vermeiden.

13.4.2 ASC() und CHR()

Den ASCII-Code eines Zeichens können Sie mit ASC() ermitteln. ASC() erhält als Parameter eine Zeichenkette, es wird davon jedoch nur das erste Zeichen ausgewertet.

Quelltext 13.3: Hallo Welt in ASCII-Werten (1)
PRINT "Die einzelnen ASCII-Codes des Strings 'Hallo Welt' lauten:"
DIM AS STRING halloWelt(...) = { "H", "a", "l", "l", "o", " ", "W", "e", "l", "t" }
FOR i AS INTEGER = 0 TO UBOUND(halloWelt)
  PRINT ASC(halloWelt(i)),
NEXT
SLEEP
Ausgabe:
72            97            108           108           111
32            87            101           108           116

Um beispielsweise den ASCII-Code des dritten Zeichens eines Strings auszugeben, gab es früher nur die Möglichkeit, einen Teilstring ab dem dritten Zeichen zu erstellen und davon den ASCII-Wert zu bestimmen. Inzwischen geht das auch einfacher — ASC() erlaubt nämlich in FreeBASIC die Angabe eines zweiten Parameters, der die Position des gewünschten Zeichens bestimmt. Das dritte Zeichen des Strings "Hallo Welt" lässt sich also auch über ASC("Hallo Welt",3) ansprechen. Eine Alternative zu Quelltext 13.3 wäre damit:

Quelltext 13.4: Hallo Welt in ASCII-Werten (2)
PRINT "Die einzelnen ASCII-Codes des Strings 'Hallo Welt' lauten:"
DIM AS STRING halloWelt = "Hallo Welt"
FOR i AS INTEGER = 1 TO LEN(halloWelt)
  PRINT ASC(halloWelt, i),
NEXT
SLEEP

Hier greife ich schon die in Kapitel 15.2.1 vorgestellte Funktion LEN() zur Ermittlung der Länge eines Strings voraus.

Für die tatsächlichen ASCII-Werte — also alle Zeichen bis zum Wert 127 — ist die Zuordnung eindeutig, Sie werden also immer, wenn Sie ASC("a") aufrufen, das Ergebnis 97 erhalten. Anders sieht es mit den Zeichen der ANSI-Erweiterung aus. Wenn Sie z. B. ASC("ä") ermitteln wollen, hängt das Ergebnis von der in Ihrer IDE verwendeten Codepage ab, also davon, nach welcher Code-Tabelle die IDE das Zeichen "ä" speichert. Erhalten Sie die Zahl 228, dann verwendet die IDE vermutlich ISO-8859-1 oder ISO-8859-15.

Die Konsole verwendet möglicherweise eine vollkommen andere Codepage als Ihre IDE. Am einfachsten sehen Sie das, wenn Sie den Schritt rückwärts gehen. Mit CHR() wird eine ASCII-Nummer in das zugehörige Zeichen umgewandelt.

PRINT "Das ASCII-Zeichen 228 ist ein "; CHR(228)
SLEEP
Ausgabe:
Das ASCII-Zeichen 228 ist ein õ.

Die Konsole unter Windows verwendet in der Regel Codepage 850; dort ist unter der Nummer 228 das õ gespeichert. Unter Linux hängt die Codepage von den System- und Konsoleneinstellungen ab. Viele Konsolen unter Linux arbeiten mit Unicode; in diesem Fall ist die Angabe 228 an dieser Stelle überhaupt nicht interpretierbar. Im Grafikfenster (darauf kommen wir in [KapGrafik] zu sprechen) kommt dagegen Codepage 437 zu tragen, diesmal unabhängig vom Betriebssystem.

Die verfügbaren Zeichen finden Sie in Anhang C aufgelistet; beachten Sie jedoch, dass die Zeichen im Konsolen-Modus von der dort eingesetzten Codepage abhängen und daher abweichen können. Wenn Sie selbst testen wollen, wie Ihr Konsolenfenster die Zeichen von 128 bis 255 interpretiert, kann Quelltext 13.5 helfen. Sollten Sie nur „undefinierte Zeichen“ erhalten, wird Ihre Konsole vermutlich gar keinen ANSI-Satz verwenden, sondern mit Unicode arbeiten.

Quelltext 13.5: ANSI-Zeichen von 128 bis 255
FOR i AS INTEGER = 128 TO 252 STEP 4   ' immer vier Zeichen pro Zeile
  PRINT i; ") "; CHR(i); TAB(20); i+1; ") "; CHR(i+1);
  PRINT TAB(40); i+2; ") "; CHR(i+2); TAB(60); i+3; ") "; CHR(i+3)
NEXT
SLEEP

Wenn Sie mehrere Nummern gleichzeitig in ihren ASCII-Code umwandeln wollen, können Sie die Werte als Parameterliste von CHR() angeben.

PRINT CHR(72, 97, 108, 108, 111, 32, 87, 101, 108, 116)
SLEEP

Zur Nutzung von Unicode ist CHR nicht geeignet, aber es gibt dafür eine Ersatzfunktion namens WCHR(). Diese gibt zu einer Unicode-Nummer das passende Zeichen zurück.

PRINT WCHR(1055, 1088, 1080, 1074, 1077, 1090, 33)
Ausgabe:
Привет!

Sie können diese Ausgabe natürlich nur dann korrekt sehen, wenn die verwendete Konsole Unicode unterstützt.

13.4.3 Binäre Kopie erstellen

ASC() und CHR() geben zwei verschiedene Interpretationen desselben Speicherzustandes wieder — einmal als UBYTE und einmal als STRING der Länge 1. Das lässt sich auch für größere Datentypen überlegen: Der Datenteil einer zwei Zeichen langen Zeichenkette belegt zwei Byte, genauso wie ein SHORT. Bei einer vier Zeichen langen Zeichenkette belegt der Datenteil denselben Speicherplatz wie ein LONG, usw. Da liegt es nahe, eine direkte Umwandlung von Zeichenketten in Zahlen zu definieren, die einer identischen Speicherbelegung entsprechen. Natürlich gibt es dazu auch eine Rückumwandlung. Sie können die Funktionen Tabelle 13.2 entnehmen.

Befehl Stringgröße Umwandlung in … Rückwandlung

CVSHORT()

2

SHORT

MKSHORT()

CVI()

4 bzw. 8

INTEGER

MKI()

CVL()

4

LONG

MKL()

CVLONGINT()

8

LONGINT

MKLONGINT()

CVS()

4

SINGLE

MKS()

CVD()

8

DOUBLE

MKD()

Tabelle 13.2: Befehle für binäre Speicherkopien

Für CVI() kann auch explizit die Bit-Zahl angegeben werden, die verwendet werden soll. Erlaubt sind die Werte 16, 32 und 64, und die Angabe erfolgt in spitzen Klammern direkt hinter dem Befehl. Ohne eine solche Angabe wird die Größe eine INTEGER verwendet, also 32 Bit beim 32-Bit-Compiler und 64 Bit beim 64-Bit-Compiler.

Als Beispiel soll CVSHORT() dienen, da es mit den kleinsten Werten arbeitet und dadurch am leichtesten nachzurechnen ist.

PRINT CVSHORT("Hi")
SLEEP

"H" besitzt den ASCII-Code 72, "i" den ASCII-Code 105. Da das "i" an zweiter Stelle steht, hat seine Codenummer den 256-fachen Stellenwert. 72 + 105·256 ergibt den vom Programm ausgegebenen Wert 26952. Dasselbe hätte auch CVI<16>() ergeben:3

PRINT CVI<16>("Hi")
SLEEP

Eine Rückumwandlung erfolgt über MKSHORT():

PRINT MKSHORT(26952)     ' oder PRINT MKI<16>(26952)
SLEEP

Übrigens: Die hier genannte Befehl machen nichts anderes als das, was Sie mit einer UNION eines Strings und einer Zahl erreichen würden. Zur Erinnerung: UNION legt mehrere Datentypen in denselben Speicherbereich. Sie können daher ein und denselben Speicherwert als verschiedene Datentypen interpretieren.

Quelltext 13.6: Binäre Kopie mit MKLONGINT und mit UNION
UNION LongintString
  AS STRING*8 s
  AS LONGINT  i
END UNION

DIM AS LONGINT testZahl = 2410100309775106630
DIM AS LongintString testUnion
testUnion.i = testZahl 
PRINT testUnion.s
PRINT MKLONGINT(testZahl)
PRINT MKI<64>(testZahl)
SLEEP
Ausgabe:
FB4ever!
FB4ever!
FB4ever!

(Leider musste ich hier schon wieder einem späteren Kapitel vorgreifen. Was es mit STRING*8 auf sich hat und warum Quelltext 13.6 mit einem normalen String nicht funktioniert, erfahren Sie in Kapitel 15.)

13.5 Fragen zum Kapitel

  1. Nennen Sie zwei Fälle, in denen eine implizite Umwandlung des Datentyps stattfindet, sowie einen Fall, in dem eine explizite Umwandlung notwendig ist.

  2. Was ist der Unterschied zwischen ASCII und ANSI, und warum ist die Unterscheidung wichtig?

  3. Sondertasten in FreeBASIC: Fragen Sie (innerhalb einer Schleife) mit INKEY Tasten ab und lassen Sie sich dazu die ASCII-Codes ausgeben. Beachten Sie dabei, dass Sondertasten (z. B. Pfeiltasten) einen String der Länge 2 zurückliefern. Lassen Sie in diesem Fall beide ASCII-Werte ausgeben und erstellen Sie sich eine Liste der (für Sie) wichtigsten Sondertasten.


Fußnoten:
1) & wandelt beide Operanden in einen String um, außer einer der beiden ist ein WSTRING; in diesem Fall wird auch der andere Operand in einen WSTRING umgewandelt.

2) ANSI = American National Standards Institute

3) Auch CVI("Hi") ohne Bitangabe würde 26952 ergeben, da zwar je nach System vier bzw. acht Zeichen erwartet würden, die fehlenden Zeichen jedoch als ASCII-Wert 0 interpretiert würden. Bei längeren Strings würde das aber durchaus einen Unterschied machen.


Kapitel 12: Prozeduren und Funktionen

Inhaltsverzeichnis

Kapitel 14: Verarbeitung von Zahlen