Kapitel 14: Verarbeitung von Zahlen

Inhaltsverzeichnis

Kapitel 16: Datei-Zugriff

15. Stringmanipulation

Wie bereits aus Kapitel 6.3.1 bekannt ist, gibt es drei verschiedene Arten von Zeichenketten: den STRING, der eine zusätzliche Speicherung seiner Länge enthält, der ZSTRING, welcher durch ein Nullbyte beendet wird, und der WSTRING, der ebenfalls durch Nullbytes beendet wird, aber Unicode-Zeichen speichern kann. Viele der in diesem Kapitel behandelten Befehle für die Stringmanipulation können sowohl bei einem STRING als auch bei einem ZSTRING verwendet werden, ohne dass Sie sich viele Gedanken um die Unterschiede zwischen beiden Speicherarten machen müssen. Einige sind auch für einen WSTRING einsetzbar, allerdings nicht alle — oft gibt es dafür eine eigene Variante des Befehls. Gehen Sie bei den folgenden Befehlen davon aus, dass die Angaben für alle drei Arten von Zeichenketten gelten, außer es ist anderweitig beschrieben.

15.1 Speicherverwaltung

15.1.1 Verwaltung eines ZSTRING

Die Speicherverwaltung bei einem ZSTRING ist die einfachste der drei Varianten. Für die Zeichenkette wird ein bestimmter Speicherbereich reserviert. Dort werden die einzelnen Byte hintereinander abgelegt. Wegen der Einfachheit in der Verwaltung verwendet man diesen Datentyp auch sehr gern zum Austausch zwischen Programmteilen, die in verschiedenen Programmiersprachen geschrieben sind.

Damit FreeBASIC weiß, wie viel Speicher für den ZSTRING belegt werden soll, muss dies im Programm explizit mitgeteilt werden, z. B. über einen ZSTRING fester Länge. Eine zweite Möglichkeit ist die Verwendung von Pointern, verbunden mit einer Speicherreservierung „von Hand“; darauf werden wir aber erst später zu sprechen kommen. Ein ZSTRING variabler Länge kann dagegen nicht angelegt werden.

ZSTRING fester Länge“ bedeutet im Übrigen nur, dass die maximale Länge der Zeichenkette festgelegt wird — und damit verbunden der reservierte Speicherplatz. Die Zeichenkette, die tatsächlich gespeichert wird, darf durchaus kürzer sein. Die Auswertung des ZSTRINGs wird immer beim ersten auftretenden Nullbyte gestoppt, alle folgenden Zeichen werden ignoriert. Allerdings macht es keinen Sinn, einen 1000 Zeichen langen ZSTRING zu reservieren, wenn man nur vorhat, höchstens zehn Zeichen lange Ketten zu speichern, da man so unnötig Speicherplatz verschwenden würde.

' DIM AS ZSTRING varlength         ' nicht moeglich (ZSTRING variabler Laenge)
DIM AS ZSTRING*5 fixlength         ' ZSTRING fester Laenge: 4 Zeichen + Nullbyte
DIM AS ZSTRING PTR zeiger          ' Pointer; Speicherreservierung noch erforderlich

Während fixlength sofort einsatzbereit ist und mit einem Wert belegt werden kann, wurde in der letzten Zeile noch keine Speicherreservierung durchgeführt. zeiger steht daher noch auf dem Ausgangswert 0, und der Versuch einer Wertzuweisung wird scheitern. Beachten Sie bei fixlength auch, dass das abschließende Nullbyte ebenfalls mit einberechnet werden muss. Um einen Text wie "Hallo" zu speichern, ist mindestens ein ZSTRING*6 erforderlich.

15.1.2 Verwaltung eines WSTRING

Bei einem WSTRING gelten für die Speicherverwaltung im Großen und Ganzen dieselben Regeln wie beim ZSTRING. Da ein WSTRING jedoch deutlich mehr (Unicode-)Zeichen beinhaltet, müssen die einzelnen Zeichen auch mehr Speicherplatz belegen. Davon merken Sie beim Programmieren allerdings nur etwas, wenn Sie sich gezielt mit der Speicherverwaltung beschäftigen, z. B. wenn Sie den Dateninhalt direkt über Speicherzugriffe manipulieren wollen oder die Gesamtgröße der Datentypen eine entscheidende Rolle spielt.

Tip Unterschiede zwischen den Plattformen:
Wie viel Platz ein Unicode-Zeichen im Speicher belegt, hängt vom Betriebssystem ab. Windows codiert die Zeichen in UCS-2 und belegt zwei Byte pro Zeichen, unter Linux kommt UCS-4 zum Einsatz, wodurch vier Byte pro Zeichen belegt werden. Das betrifft auch das abschließende Nullbyte, das in Wirklichkeit aus zwei bzw. viel Nullbyte besteht.

Der Umgang mit WSTRING hält am Anfang einige Schwierigkeiten bereit, die nicht immer leicht zu durchschauen sind. Zunächst einmal kann FreeBASIC nur dann korrekt mit Unicode-Dateien umgehen, wenn die entsprechende Datei in Unicode mit Byte Order Mark (BOM) gesetzt ist. Eine Ausgabe über Konsole funktioniert natürlich nur, wenn dies von der Konsole auch unterstützt wird, und eine korrekte Ausgabe im Grafikfenster ist ohne externe Bibliotheken gar nicht erst möglich. Daher empfehle ich Ihnen, sich zuerst gründlich mit STRING und ZSTRING zu beschäftigen, bevor Sie sich an WSTRING wagen.

15.1.3 Verwaltung eines STRING

Ein STRING fester Länge wird im Prinzip genauso verwaltet wie ein ZSTRING, nur dass ein Nullbyte nicht das Ende markiert. Ein STRING*10 ist immer 10 Byte lang, egal wie sein Inhalt lautet. Ein ZSTRING*10 dagegen belegt immer einen Speicherbereich von 10 Byte, kann aber 0 bis 9 Byte lang sein.

Warning Achtung:
Wenn Sie einen STRING fester Länge als Parameter einer Funktion oder Prozedur verwenden, wird er in aller Regel als ZSTRING übergeben, und die Terminierung durch das Nullbyte schlägt wieder zu.

Bei einem STRING variabler Länge muss, im Gegensatz zu den vorigen Datentypen, die aktuelle Länge mitgespeichert werden. Außerdem läuft die Reservierung und Freigabe des benötigten Speicherbereichs vollkommen automatisch. Dies ist nur möglich, weil der Zeiger auf den STRING gar nicht auf den STRING-Inhalt selbst zeigt, sondern auf einen Bereich mit Metadaten. Damit ist der STRING zum einen intern der komplizierteste Datentyp für Zeichenketten, zum anderen aber auch der am leichtesten zu bedienende.

Der STRING-Pointer zeigt auf einen drei INTEGER großen Bereich (also je nach Betriebssystem 12 bzw. 24 Bit). Dort ist der Reihe nach die Adresse der Zeichenkette, die aktuelle Länge der Zeichenkette und die Größe des aktuell für die Zeichenkette reservierten Speicherbereichs hinterlegt. Wenn Sie eine STRING-Variable anlegen (und noch keine Wert-Zuweisung vornehmen), benötigt der STRING den Speicherplatz von drei INTEGER für die Metadaten, die alle drei den Wert 0 besitzen. Sobald dem STRING eine Zeichenkette zugewiesen wird, reserviert FreeBASIC einen zusätzlichen Speicherbereich für den Inhalt der Zeichenkette und aktualisiert die Metadaten entsprechend.

Bei einer sich häufig ändernden STRING-Länge hätte dieses Verfahren den Nachteil, dass der Speicherbereich für die Zeichenkette immer wieder gewechselt werden müsste und es dadurch schnell zu einer Speicherfragmentierung käme. Um dem entgegenzuwirken, reserviert FreeBASIC in der Regel mehr Speicherplatz für die Zeichenkette, als dringend erforderlich. Insbesondere bei einer Verkleinerung des STRINGs wird kein neuer Speicherplatz gesucht, sondern der bisher verwendete Bereich beibehalten. Nur wenn Sie der Variablen einen Leerstring zuweisen, wird der Speicherplatz für die Zeichenkette wieder komplett freigegeben.

Tip Hinweis:
Um eine bessere Kompatibilität zwischen STRING und ZSTRING (und damit zu externen Bibliotheken) zu erhalten, hängt FreeBASIC — im Gegensatz zu QuickBASIC — an das Ende eines STRINGs fester und variabler Länge ein zusätzliches Nullbyte an. Dieses terminiert die Zeichenkette jedoch nicht und tritt nur in Erscheinung, wenn Sie direkt auf die Speicherbereiche zugreifen oder wenn Sie einen Datenaustausch zwischen FreeBASIC und QuickBASIC vornehmen wollen.

15.2 Stringfunktionen

15.2.1 LEN(): Länge eines Strings ermitteln

Kurz und bündig: LEN() gibt zurück, wie viele Zeichen ein String enthält. Dass ein WSTRING mehrere Byte Speicherplatz pro Zeichen benötigt, spielt dabei keine Rolle; zurückgegeben wird die Anzahl der Zeichen, nicht der Speicherbedarf.

Quelltext 15.1: LEN() zur Ermittlung der String-Länge
DIM AS STRING     halloString  = "Hallo, Welt!"
DIM AS WSTRING*99 halloWString = "Hallo, Welt1"
PRINT "STRING:                "; LEN(halloString)
PRINT "WSTRING fester Laenge: "; LEN(halloWString)

' und weil das nun doch etwas zu einfach gewesen waere
DIM AS STRING*99  halloFixString  = "Hallo, Welt!"
PRINT "STRING fester Laenge:  "; LEN(halloFixString)
SLEEP
Ausgabe:
STRING:                 12
WSTRING fester Laenge:  12
STRING fester Laenge:   99

Wäre auch zu schön gewesen. Aber STRINGs fester Länge haben eben wirklich eine feste Länge, ganz egal, welchen Inhalt sie besitzen. WSTRING und ZSTRING dagegen enden immer beim ersten auftretenden Nullbyte.

15.2.2 Die Funktionen SPACE() und STRING()

Ja, es gibt eine Stringfunktion namens STRING(). Sie gibt ganz einfach einen String zurück, der aus lauter gleichen Zeichen besteht. In Kapitel 12.3.3 wurde bereits SPACE() vorgestellt, das eine Zeichenkette aus einer beliebigen Anzahl an Leerzeichen erstellt. STRING() bewirkt dasselbe, nur dass Sie sich das Füllzeichen selbst aussuchen können. Dieses Füllzeichen kann als ASCII-Code oder als String angegeben werden (von einem solchen String wird aber nur das erste Zeichen verwendet).

Quelltext 15.2: STRING() und SPACE()
' erzeuge eine Zeichenkette aus 20 Leerzeichen
DIM AS STRING leer20
leer20 = STRING(20, " ")
' bewirkt dasselbe wie    leer20 = SPACE(20)
' oder auch               leer20 = STRING(20, 32)
' Der ASCII-Code des Leerzeichens ist 32.

' Textunterstreichung
DIM AS STRING satz = "Dieser Satz ist unterstrichen."
PRINT satz
PRINT STRING(LEN(satz), "=")
SLEEP
Ausgabe:
Dieser Satz ist unterstrichen.
==============================

Die Länge der Unterstreichung wird automatisch aus der Länge des Satzes ermittelt. Eine solche Art von Textunterstreichung wäre auch in Quelltext 12.10 recht praktisch.

Wenn Sie statt eines Strings einen WSTRING erstellen wollen, der aus lauter gleichen Zeichen besteht, greifen Sie stattdessen auf die Funktionen WSTRING() bzw. WSPACE() zurück.

15.2.3 LEFT(), RIGHT() und MID()

FreeBASIC stellt einige Funktionen bereit, um einen Teilstring auszulesen. Die gängigsten sind LEFT(), RIGHT() und MID(). Mit LEFT() erhalten Sie die ersten x Zeichen des Strings, mit RIGHT() die letzten x Zeichen — mit MID() lesen Sie eine Zeichenkette mitten aus einem String aus.

LEFT() erwartet als Parameter zunächst einmal den Originalstring und des Weiteren die Anzahl der Zeichen, die zurückgegeben werden sollen. RIGHT() funktioniert prinzipiell genauso, nur dass nicht die vorderen, sondern die hinteren Zeichen zurückgegeben werden.

Quelltext 15.3: LEFT() und RIGHT()
DIM AS STRING halloWelt = "Hallo, Welt!"
PRINT LEFT(halloWelt, 5)                   ' die ersten 5 Zeichen
DIM AS STRING welt = RIGHT(halloWelt, 5)   ' die letzten 5 Zeichen
PRINT welt
PRINT ">"; LEFT(halloWelt, 50); "<"        ' die ersten 50 Zeichen (bricht
SLEEP                                      '   nach dem letzten Zeichen ab)
Ausgabe:
Hallo
Welt!
>Hallo, Welt!<

Die beiden Funktionen führen eine automatische Überprüfung der Stringlänge durch. Wenn Sie mehr Zeichen auslesen wollen, als der String Zeichen hat, erhalten Sie den kompletten String zurück. Sie können also nicht versehentlich über den Speicherbereich hinaus lesen und dadurch einen Programmabsturz riskieren. Die dadurch erhaltene Sicherheit wird natürlich mit Geschwindigkeit bezahlt: durch die notwendige Überprüfung sind die beiden Funktionen relativ langsam (was aber nur ins Gewicht fällt, wenn der Aufruf sehr häufig wiederholt wird).

Auch MID() führt eine solche Sicherheitsüberprüfung durch. MID() erwartet drei Parameter: den Originalstring, die Startposition für den Lesevorgang und die Anzahl der Zeichen, die ausgelesen werden sollen. Wenn Sie mehr Zeichen lesen wollen, als zur Verfügung stehen, gibt MID() ganz einfach den Rest des Strings ab der Startposition bis zum Ende aus. Sie können sich aber auch von Vornherein alle Zeichen von einem Startpunkt bis zum Stringende ausgeben lassen — lassen Sie dazu einfach die Längenangebe weg oder geben Sie eine negative Länge an.

Quelltext 15.4: MID
DIM AS STRING halloWelt = "Hallo, Welt!"
PRINT MID(halloWelt, 5, 4)                 ' 4 Zeichen ab dem 5. Zeichen
DIM AS STRING welt = MID(halloWelt, 8)     ' alles ab dem 8. Zeichen
PRINT welt
PRINT MID(halloWelt, 8, 50)                ' alles ab dem 8. Zeichen
SLEEP                                      '   (weil der String zu kurz ist)
Ausgabe:
o, W
Welt!
Welt!

Wenn Sie „unsinnige“ Werte einsetzen (z. B. indem Sie bei LEFT() oder RIGHT() eine negative Anzahl an Zeichen auslesen wollen oder bei MID als Startwert keine positive Zahl eingeben), erhalten Sie als Rückgabe einen Leerstring. Negative Zahlen sind nur als Längenangabe für MID() erlaubt, um den kompletten Reststring ab einem bestimmten Startwert zu erhalten.

15.2.4 Zeichenketten modifizieren

Die oben genannten Funktionen geben einen Teilstring zurück; der ursprüngliche String wird dabei nicht verändert. MID gibt es jedoch auch als Anweisung, um einen Teil des Strings durch einen anderen zu ersetzen. Weitere Befehle zur direkten Modifikation einer Zeichenkette sind LSET und RSET

Die drei Befehle stammen aus frühen QBasic-Zeiten und sind aus Kompatibilitätsgründen immer noch in FreeBASIC verfügbar. Persönlich würde ich von einer Verwendung abraten und sie durch flexiblere Prozeduren ersetzen (dazu später ein Beispiel in Quelltext 15.5). Der Vollständigkeit halber werden sie hier aber aufgeführt.

15.2.4.1 MID als Anweisung

Als Anweisung besitzt MID einen etwas anderen Aufbau:

MID(Zeichenkette, Startzeichen, Zeichenzahl) = Ersetzungsstring

Zeichenzahl kann auch weggelassen werden; dann richtet sich die Anzahl der ersetzten Zeichen nach der Länge des Ersetzungsstrings.

Quelltext 15.5: Teilstring ersetzen mit MID
DIM AS STRING halloWelt = "Hallo, Welt!"
MID(halloWelt, 9, 3) = "ald"               ' Teilstring ersetzen
' MID(halloWelt, 9) = "ald"                ' bewirkt dasselbe
PRINT halloWelt
MID(halloWelt, 9, 3) = "ienerle"           ' Teilstring ersetzen
PRINT halloWelt
SLEEP
Ausgabe:
Hallo, Wald!
Hallo, Wien!

Gut, das war jetzt nicht besonders originell. Aber es sollte deutlich geworden sein, was passiert ist: Ab dem neunten Zeichen wurden drei Zeichen (ursprünglich “elt“) durch “ald“ ersetzt. Leider hat diese Methode eine deutliche Einschränkung: Die Länge des Originalstrings kann damit nicht verändert werden. Das heißt: Sie können immer nur einen Teilstring durch einen gleich langen String ersetzen. Das sieht man im zweiten Ersetzungsversuch: Da nur drei Zeichen ersetzt werden sollen, werden auch nur die ersten drei Zeichen aus dem Ersetzungsstring genommen. Wenn übrigens der Ersetzungsstring zu kurz ist, werden auch nur so viele Zeichen ersetzt, wie vorhanden sind.

DIM AS STRING text = "abcdefgh"
MID(text, 3, 5) = "XY"
PRINT text
SLEEP
Ausgabe:
abXYefgh

Meiner Meinung nach muss man sinnvolle Anwendungsmöglichkeiten für die Anweisung MID schon ziemlich konstruieren. Als Beispiel dazu eine Möglichkeit zur Textzensur:

Quelltext 15.6: Textzensur mit MID
DIM AS STRING sternchen = STRING(500, "*")
DIM AS STRING anweisung = "Geben Sie den Benutzernamen admin " _
                          & "und das Passwort abc123 ein."
MID(anweisung, 29, 5) = sternchen
MID(anweisung, 52, 6) = sternchen
PRINT anweisung
SLEEP
Ausgabe:
Geben Sie den Benutzernamen ***** und das Passwort ****** ein.

Hier spielt es bei der Erstellung des Ersetzungsstrings sternchen keine Rolle, wie viele Zeichen später gebraucht werden — sternchen stellt einfach ausreichend viele Zeichen für alle Fälle bereit. Wie viele dann tatsächlich im Ausgangsstring eingesetzt werden, entscheidet sich direkt in der Anweisung MID.

15.2.4.2 LSET und RSET

LSET und RSET füllen einen String links- bzw rechtsbündig durch einen anderen String auf.

LSET Ziel, Quelle

Der Inhalt von Ziel wird durch Quelle ersetzt, jedoch bleibt die Länge von Ziel gleich. Ist Quelle länger als Ziel, dann werden nur die ersten Zeichen von Quelle verwendet (so viele, wie in Ziel hineinpassen). Ist dagegen Ziel länger, dann werden die fehlenden Zeichen durch Leerzeichen aufgefüllt, und zwar bei LSET hinten (der neue Inhalt von Ziel ist dann linksbündig) und bei RSET vorn (der neue Inhalt von Ziel ist dann rechtsbündig).

Quelltext 15.7: LSET und RSET
DIM AS STRING quelle, ziel = "0123456789"
PRINT ziel
' linksbuendiger Text
quelle = "links"
LSET ziel, quelle
PRINT ziel
' rechtsbuendiger Text
quelle = "rechts"
RSET ziel, quelle
PRINT ziel
SLEEP
Ausgabe:
0123456789
links
    rechts

15.2.5 Führende/angehängte Zeichen entfernen: LTRIM(), RTRIM() und TRIM()

Kommen wir wieder zu deutlich praktischeren Funktionen. LTRIM() (Left TRIM) entfernt bestimmte Zeichen vom Anfang der Zeichenkette. Zunächst ist die Funktion dazu gedacht, führende Leerzeichen zu entfernen, jedoch kann auch jedes beliebige Zeichen oder auch eine bestimmte Zeichenfolge entfernt werden. RTRIM() (Right TRIM) arbeitet analog dazu von rechts und entfernt Zeichen oder Zeichenfolgen am Ende der Zeichenkette.

Quelltext 15.8: LTRIM() und RTRIM()
DIM AS STRING eingabe = "     Die besten Reden sind die schnell endenden"

' Ohne weitere Angabe werden die Leerzeichen entfernt:
PRINT LTRIM(eingabe)

' Es kann auch ein anderes Zeichen entfernt werden:
PRINT RTRIM(eingabe, "n")

' Entfernung der Zeichenfolge 'den':
PRINT RTRIM(eingabe, "den")
SLEEP
Ausgabe:
Die besten Reden sind die schnell endenden
     Die besten Reden sind die schnell endende
     Die besten Reden sind die schnell en

In der letzten Zeile wurde die Zeichenfolge "den" zweimal abgeschnitten, weil sie zweimal vorkam. Jetzt endet der Text zwar wieder auf "en", aber nicht auf "den", womit nicht weiter gekürzt wird. Beachten Sie auch, dass die Variable eingabe das ganze Programm hinweg denselben Wert behält. Die Kürzung findet nur beim zurückgegebenen Funktionswert statt (der in diesem Fall nicht gespeichert, sondern direkt über PRINT ausgegeben wird).

Wenn Sie nicht die genaue Zeichenfolge "den" abschneiden wollen, sondern jedes Auftreten eines der Buchstaben d, e und n, können Sie das Schlüsselwort ANY benutzen.

Quelltext 15.9: RTRIM() mit Schlüsselwort ANY
DIM AS STRING eingabe = "     Die besten Reden sind die schnell endenden"

' entfernt am Ende jedes d, e, l, n oder Leerzeichen
PRINT RTRIM(eingabe, ANY "deln ")
SLEEP
Ausgabe:
     Die besten Reden sind die sch

ANY bietet sich auch an, wenn Sie jede Art von Whitespace entfernen wollen. Dazu gehören Tabulatoren (ASCII-Code 9), Zeilenumbruch (je nach Betriebssystem ASCII-Codes 10 und 13) und Leerzeichen (ASCII-Code 32).

DIM AS STRING eingabe
' alle Whitespace-Zeichen am Anfang entfernen
PRINT LTRIM(eingabe, ANY CHR(9, 10, 13, 32))
SLEEP

TRIM() vereinigt die beiden Funktionen LTRIM() und RTRIM() und entfernt die gewünschten Zeichen sowohl am Anfang als auch am Ende der Zeichenkette. Auch hier kann ANY eingesetzt werden.

15.2.6 Umwandlung in Groß- bzw. Kleinbuchstaben: UCASE() und LCASE()

UCASE() (Upper CASE) wandelt einen String komplett in Großbuchstaben um, LCASE() (Lower CASE) in Kleinbuchstaben. Natürlich können nur die Zeichen umgewandelt werden, zu denen ein Groß- bzw. Kleinbuchstabe existiert — das wären zunächst einmal die lateinischen Buchstaben a-z. Umlaute oder Buchstaben mit Akzent werden nicht umgewandelt. Sie bleiben, wie auch alle Ziffern und Sonderzeichen, unverändert.

Quelltext 15.10: Alles gross oder alles klein
DIM AS STRING text = "Mal GROSS, mal klein!"
' alles in Gross
PRINT UCASE(text)
' alles in Klein:
PRINT LCASE(text)
SLEEP

Umlaute gehören genauso wie andere länderspezifische Sonderbuchstaben nicht zum ASCII-Zeichensatz. Die Verwendung in einem String wirft daher Probleme auf. Einen kurzen Abriss über das Thema Umlaute lesen Sie in Kapitel 15.3.

15.2.7 Teilstring suchen: INSTR() und INSTRREV()

INSTR() durchsucht einen String nach einem Teilstring und gibt die erste Fundstelle zurück. Optional kann eine Startposition angegeben werden, ab der die Suche beginnt. Wie bei TRIM() kann der Suchstring aus mehreren Zeichen bestehen, und die Angabe des Schlüsselwortes ANY ermöglicht die Suche nach einem beliebigen Zeichen aus dem Suchstring.

Quelltext 15.11: Teilstring suchen
DIM AS STRING text = "Ich bin ein Satz mit sechs Leerzeichen."
DIM AS INTEGER position1, position2

' erstes Leerzeichen suchen
position1 = INSTR(text, " ")
PRINT "Das erste  Leerzeichen befindet sich an Position"; position1
' zweites Leerzeichen suchen
PRINT "Das zweite Leerzeichen befindet sich an Position";
PRINT INSTR(position1+1, text, " ")
PRINT

' Verwendung von ANY
PRINT "Suche Zeichenkette 'ein' - Fundstelle:"; INSTR(text, "ein")
PRINT "Suche nach e, i oder n   - Fundstelle:"; INSTR(text, ANY "ein")
SLEEP
Ausgabe:
Das erste  Leerzeichen befindet sich an Position 4
Das zweite Leerzeichen befindet sich an Position 8

Suche Zeichenkette 'ein' - Fundstelle: 9
Suche nach e, i oder n   - Fundstelle: 6

Das erste Leerzeichen findet sich beim vierten Zeichen des Strings. Danach sucht das Programm nach einem Leerzeichen ab der fünften Stelle. Hätten Sie ab der vierten Stelle gesucht, hätten Sie dasselbe Leerzeichen ein zweites Mal gefunden.

Bei der Suche nach dem String "ein" wird das Programm ab dem neunten Zeichen fündig, da hier die komplette Zeichenkette zum ersten Mal auftritt. Mit dem Schlüsselwort ANY erhalten Sie dagegen die Position des ersten auftretenden "i", denn "e" und "n" treten später auf.

INSTRREV() arbeitet ähnlich wie INSTR(), durchsucht den String jedoch von hinten (gibt also zurück, wann der Suchstring das letzte Mal auftritt). Die beiden Befehle unterscheiden sich außerdem in der Angabe des optionalen Parameters für die Startposition. Während dieser Parameter bei INSTR() vorn steht, wird er bei INSTRREV() nach hinten gestellt.

Note Hinweis:
Die Angabe eines optionalen Parameters am Beginn der Parameterliste ist untypisch für FreeBASIC. Genau genommen handelt es sich bei INSTR() nicht um eine Funktion mit optionalem Parameter, sondern um eine überladene Funktion mit zwei verschiedenen Parameterlisten. Das liegt an der Vorgabe von QuickBASIC, die in FreeBASIC übernommen wurde. INSTRREV() ist dagegen neu in FreeBASIC, und es verwendet das Standard-Verhalten von FreeBASIC.
Quelltext 15.12: Teilstring rückwärts suchen
DIM AS STRING text = "Ich bin ein Satz mit sechs Leerzeichen."
DIM AS INTEGER position1, position2

' letztes Leerzeichen suchen
position1 = INSTRREV(text, " ")
PRINT "Das letzte Leerzeichen befindet sich an Position"; position1
' vorletztes Leerzeichen suchen
PRINT "Das vorletzte Leerzeichen befindet sich an Position";
PRINT INSTRREV(text, " ", position1-1)
PRINT

' Verwendung von ANY
PRINT "Suche Zeichenkette 'ein' - Fundstelle:"; INSTRREV(text, "ein")
PRINT "Suche nach e, i oder n   - Fundstelle:"; INSTRREV(text, ANY "ein")
SLEEP
Ausgabe:
Das letzte Leerzeichen befindet sich an Position 27
Das vorletzte Leerzeichen befindet sich an Position 21

Suche Zeichenkette 'ein' - Fundstelle: 9
Suche nach e, i oder n   - Fundstelle: 38

15.3 Umlaute und andere Sonderbuchstaben

15.3.1 Problemstellung: Darstellung von Sonderzeichen

Die Verwendung von Umlauten wirft leider einige Schwierigkeiten auf. Wie in Kapitel 13.4 bereits erwähnt, ist nur der ASCII-Zeichensatz genormt. Ob und an welcher Stelle ein bestimmtes Sonderzeichen gespeichert ist, hängt von der jeweiligen Codepage ab. Leider verwenden die gängigen Editorn in der Regel eine andere Codepage als die Programmkonsole.

' Problemlose Umwandlung in Grossbuchstaben:
PRINT UCASE("unproblematische Umsetzung!")
' Umlaute bereiten Schwierigkeiten:
PRINT UCASE("ärgerliche Schwierigkeiten?")
SLEEP

Welche Ausgabe Sie beim zweiten Befehl erhalten, hängt von zwei Faktoren ab. Am wahrscheinlichsten lesen Sie etwas wie

Ausgabe:
õRGERLICHE SCHWIERIGKEITEN?

Wurde das "ä" falsch umgewandelt? Nein, es wurde ohne Änderung übernommen, aber Sie sehen es in einer falschen Zeichencodierung. Unter Windows arbeitet die Konsole in der Zeichencodierung IBM850 (sie unterstützt inzwischen aber auch Unicode), der Editor wird dagegen in aller Regel mit ISO-8859-1 oder ISO-8859-15 arbeiten (aber auch immer mehr moderne Editoren unterstützen Unicode).

15.3.2 Erster Lösungsversuch: Speicherformat anpassen

Da sich hier zwei unterschiedliche Formate in die Quere kommen, ist eine naheliegende Lösung, eines der beiden Formate anzupassen. Wenn Sie die Datei im Format IBM850 speichern und kompilieren, erhalten Sie unter Windows zumindest folgende Ausgabe:

Ausgabe:
äRGERLICHE SCHWIERIGKEITEN?

Das "ä" wurde schon einmal richtig erkannt, allerdings weiß FreeBASIC nicht, dass es sich um ein Zeichen handelt, für das auch ein Großbuchstabe existiert — geschweige denn, dass es den zugehörigen Großbuchstaben kennen würde. UCASE() und LCASE() schlägt daher für solche Zeichen fehl, Sie müssten sich dazu eine eigene Umwandlungsroutine schreiben.

Interessant wird es aber, wenn Sie den Quelltext in UTF-8 mit BOM speichern (die Byte Order Mark ist hier von entscheidender Bedeutung!) Dann nämlich wird die Zeichenkette nicht als STRING, sondern als WSTRING behandelt, und hier kennt der Compiler für Umlaute und Buchstaben mit Akzenten die korrekte Zuordnung zwischen Groß- und Kleinbuchstaben. Dann erhalten Sie also tatsächlich die korrekte Ausgabe

Ausgabe:
ÄRGERLICHE SCHWIERIGKEITEN?

Allerdings mit einem großen Aber. Eine Wertzuweisung in eine STRING-Variable macht das wieder zunichte. Zum Glück funktioniert jedoch eine Zuweisung in eine WSTRING-Variable:

' Das funktioniert nicht, wie es soll:
DIM AS STRING     test1 = "ärgerliche Schwierigkeiten?"
' Das funktioniert dagegen:
DIM AS WSTRING*28 test2 = "ärgerliche Schwierigkeiten?"

PRINT UCASE(test2)
SLEEP

Leider unterstützen die gängigen FreeBASIC-IDEs keine Speicherung im UTF-Format. Empfehlenswert ist in diesem Bereich der Editor Geany1 , der zwar keine spezielle FreeBASIC-Funktionionalität besitzt, aber zumindest ein Syntax-Highlighting für FreeBASIC sowie einige allgemein nützliche Programmierhilfen bereitstellt. Ein Problem bleibt damit aber immer noch: Wenn Sie Ihren Quelltext veröffentlichen, können Sie nicht sicher sein, dass ihn der Benutzer genau mit der Zeichencodierung speichert, die Sie gerne hätten.

15.3.3 Zweiter Lösungsversuch: Sonderzeichen als Konstanten

Wenn Sie sicher gehen wollen, dass Ihnen die Speichermethode nicht dazwischenfunkt, sollten Sie auf Sonderzeichen im Quelltext verzichten. Stattdessen kann jedes in der Codetabelle vorhandene Zeichen über seine Codenummer angesprochen werden. Um eine einheitliche Lösung für Windows und Linux zu bieten,2 greifen wir etwas vor und nutzen für das folgende Beispiel das Grafikfenster, das Sie in [KapGrafik] kennenlernen werden. PRINT-Ausgaben im Grafikfenster nutzen den FreeBASIC-internen Grafikfont und damit festdefiniert und unveränderlich die Codepage 437 (DOS-US, der Original-Zeichensatz des IBM-PC ab 1981).

SCREENRES 400, 300
PRINT "Sonderzeichen ohne Fehlerm" & CHR(148) & "glichkeit"
SLEEP
Ausgabe:
Sonderzeichen ohne Fehlermöglichkeit

Das Beispiel funktioniert auf allen unterstützten Betriebssystemen und bei jeder Speichercodierung, denn der Quellcode nutzt nur ASCII-Zeichen. Natürlich gibt es sehr wohl eine Fehlermöglichkeit, und zwar, dass Sie sich bei der Codenummer CHR(148) vertun oder schlichtweg vertippen. Wenn Sie nicht alle wichtigen Codenummern auswendig im Kopf haben, ist es schwer, auf die Schnelle zu erkennen, ob der richtige Buchstabe verwendet wurde. Etwas leichter wird es mit Verwendung von Konstanten:

CONST UML_AE = CHR(132), UML_OE = CHR(148), UML_UE = CHR(129)

SCREENRES 400, 300
PRINT "So w" & UML_AE & "re es " & UML_UE & "berhaupt sch" & UML_OE & "ner."
SLEEP

Sicherlich auch etwas holprig zu lesen, aber einfacher als mit den direkten CHR-Befehlen. Einen weiteren Vorteil haben die Konstanten: Wenn sich die Werte einmal ändern, beispielsweise weil Sie eine Ausgabe in der Konsole wünschen, müssen Sie die Anpassung nur an einer Stelle vornehmen, anstatt den kompletten Quelltext nach Änderungen abzusuchen. Beachten Sie dabei die vorhin erwähnten Probleme der Plattformabhängigkeit bei der Konsolenausgabe: Mithilfe von Präprozessoren (siehe [KapPreprocessorPlattform]) können Sie die Werte der Konstanten passend zum verwendeten Betriebssystem gestalten.

15.4 Escape-Sequenzen

Unter Escape-Sequenzen versteht man Zeichenkombinationen, die eine Sonderfunktion ausführen. In FreeBASIC werden sie eingesetzt, um in einem String auf spezielle ASCII-Zeichen wie den Zeilenumbruch oder die Tabulatortaste zuzugreifen.

Escape-Sequenzen werden in einem String nur dann interpretiert, wenn dem beginnenden Anführungszeichen des Strings ein Ausrufezeichen vorausgeht. Das Escape-Zeichen ist der Backslash \ (ASCII-Code 92). Tritt im String ein Backslash auf, so wird der folgende Ausdruck zuerst interpretiert. Die einfachste Form ist ein Backslash gefolgt von einer Zahl — in diesem Fall wird das Zeichen ausgegeben, dessen ASCII-Codenummer mit der Zahl übereinstimmt. Die Zahl kann dabei in jedem Zahlensystem angegeben werden, wenn Sie dafür das zugehörige Präfix verwenden (vgl. Kapitel 14.2.1). Umlaute (im Grafikfenster) lassen sich damit auch folgendermaßen schreiben:

SCREENRES 400, 300
' mit ASCII-Codes im Dezimalsystem:
PRINT !"So w\132re es \129berhaupt sch\148ner."
' mit ASCII-Codes im Hexadezimalsystem:
PRINT !"So w\&h84re es \&h81berhaupt sch\&h94ner."
' mit ASCII-Codes im Oktalsystem:
PRINT !"So w\&o204re es \&o201berhaupt sch\&o224ner."
SLEEP

Durch das Ausrufezeichen wird die Interpretation der Escape-Sequenzen aktiviert, der Umlaut "ä" (ASCII-Code 132, hexadezimal &h84, oktal &o204) kann nun mit Hilfe des Backslash und der Codenummer aufgerufen werden.

Einige besondere Steuerungszeichen besitzen eine spezielle Escape-Sequenz:

  • \r: wie CHR(13) (carriage-return: Wagenrücklauf)

  • \n und \l: wie CHR(10) (line-feed: Zeilenvorschub).
    \r\n ergibt ein CRLF, unter Windows die EDV-Version eines Zeilenumbruchs.

  • \a: wie CHR(7) (Bell: Glocke)

  • \b: wie CHR(8) (Backspace: Rücktaste)

  • \t: wie CHR(9) (Tab: Tabulator)

  • \v: wie CHR(11) (vtab: vertikaler Tabulator)

  • \f: wie CHR(12) (formfeed: Seitenumbruch)

  • \": wie CHR(34) (doppeltes Anführungszeichen ")

  • \': wie CHR(39) (einfaches Anführungszeichen ')

Alle anderen Zeichen hinter dem Escape-Zeichen werden so interpretiert, wie sie im Ausdruck stehen. \\ wird also zu \.

PRINT !"C:\\\\Programme\\meinTest.exe\tTestprogramm \"Alpha\"\r\nViel Erfolg!"
Ausgabe:
C:\\Programme\meinTest.exe      Testprogramm "Alpha"
Viel Erfolg!

Escape-Sequenzen funktionieren ausschließlich als Präprozessor, d. h. sie werden direkt vor dem Compilieren übersetzt. Das bedeutet konkret, dass die Markierung eines Escape-Strings durch das Ausrufezeichen ausschließlich vor dem beginnenden Anführungszeichen möglich ist. Eine Variable kann nicht nachträglich durch ein Ausrufezeichen zu einem Escape-String umgebaut werden.

DIM AS STRING test1 = !"Text mit\nZeilenumbruch"
PRINT test1                                        ' funktioniert

DIM AS STRING test2 = "Text mit\nZeilenumbruch"
PRINT !test2                                       ' erzeugt einen Compiler-Fehler

15.5 Fragen zum Kapitel

  1. Schreiben Sie ein Programm, das den Benutzer auffordert, seinen Vor- und Nachnamen durch Leerzeichen getrennt einzugeben. Zerlegen Sie dann die Eingabe und speichern Sie Vor- und Nachname in zwei getrennten Variablen.

  2. Schreiben Sie eine Funktion, die in einem übergebenen String nach einer bestimmten Zeichenkette sucht und sie durch eine andere ersetzt. Als Anregung könnten Sie alle auftretenden "ä" durch "ae" ersetzen.

  3. Im BASIC-Dialekt Omikron BASIC gab es eine Funktion namens MIRROR$(), die einen String entgegennahm und den rückwärts geschriebenen String zurückgab. Auch wenn es für diese Funktion nicht allzu viele Einsatzszenarien gibt, stellt sie doch eine hervorragende Übungsaufgabe dar.


2) Selbst unter Windows ist nicht sichergestellt, dass der Benutzer die Standardkonsole verwendet und dass Codepage 850 eingestellt ist. Die Ausgabe von ANSI-Zeichen in der Konsole ist in jedem Fall mit dem Risiko einer falschen Darstellung verbunden.


Kapitel 14: Verarbeitung von Zahlen

Inhaltsverzeichnis

Kapitel 16: Datei-Zugriff