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 ZSTRING
s 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.
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.
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 STRING
s 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.
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 STRING s 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.
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
STRING: 12
WSTRING fester Laenge: 12
STRING fester Laenge: 99
Wäre auch zu schön gewesen. Aber STRING
s 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).
' 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
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.
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)
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.
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)
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.
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
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
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:
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
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).
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
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.
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
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.
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
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.
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.
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
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.
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. |
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
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
õ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:
ä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
Ä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
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
: wieCHR(13)
(carriage-return: Wagenrücklauf) -
\n
und\l
: wieCHR(10)
(line-feed: Zeilenvorschub).
\r\n
ergibt ein CRLF, unter Windows die EDV-Version eines Zeilenumbruchs. -
\a
: wieCHR(7)
(Bell: Glocke) -
\b
: wieCHR(8)
(Backspace: Rücktaste) -
\t
: wieCHR(9)
(Tab: Tabulator) -
\v
: wieCHR(11)
(vtab: vertikaler Tabulator) -
\f
: wieCHR(12)
(formfeed: Seitenumbruch) -
\"
: wieCHR(34)
(doppeltes Anführungszeichen ") -
\'
: wieCHR(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!"
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
-
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.
-
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. -
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.
Fußnoten:
1) https://geany.org
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.