Die Boost C++ Bibliotheken


Kapitel 5: String-Verarbeitung


Inhaltsverzeichnis

Dieses Buch ist unter einer Creative Commons-Lizenz lizensiert.

Die englische Übersetzung dieses Buchs ist im Buchhandel verfügbar! Es handelt sich dabei um eine aktualisierte Version, die auf den Boost C++ Bibliotheken 1.47.0 vom Juli 2011 basiert. So wurde das Buch im Hinblick auf neue Versionen verschiedener Boost Bibliotheken aktualisiert (zum Beispiel auf Boost.Spirit 2.x, Boost.Signals 2 und Boost.Filesystem 3). Mit insgesamt 38 Boost Bibliotheken werden außerdem mehr Bibliotheken als je zuvor vorgestellt (neu sind zum Beispiel Boost.CircularBuffer, Boost.Intrusive und Boost.MultiArray). Das Buch kann bei Amazon oder anderen Buchhändlern bestellt werden. Zusätzliche Informationen zum Buch finden Sie beim Verlag XML Press.


5.1 Allgemeines

Der C++ Standard stellt die Klasse std::string zur Verfügung, die zahlreiche Methoden anbietet, um Strings zu verarbeiten. Zu diesen zählen zum Beispiel Methoden, um Strings nach einem Zeichen zu durchsuchen oder um auf einen Teil eines Strings zuzugreifen und ihn zurückzugeben. Auch wenn std::string mit mehr als 100 Methoden zu den eher aufgeblähten Klassen des C++ Standards zählt, vermissen Entwickler im Alltag doch viele Methoden, wenn sie Strings verarbeiten wollen. Während zum Beispiel in Java oder .NET Methoden zur Verfügung stehen, um einen String in Großbuchstaben umzuwandeln, bietet std::string keine derartige Methode an. Die Boost C++ Bibliotheken, die Sie in diesem Kapitel kennenlernen werden, versuchen, diese Lücke zu schließen.


5.2 Locale

Bevor Sie die Boost C++ Bibliotheken kennenlernen, die das Arbeiten mit Strings wesentlich vereinfachen, soll ein Blick auf die Locale-Komponente des C++ Standards geworfen werden. Denn viele Funktionen, die Sie in diesem Kapitel kennenlernen werden, erwarten als einen zusätzlichen Parameter ein Locale.

Sinn und Zweck der im C++ Standard definierten Locale-Komponente ist es, einen Kulturkreis näher zu beschreiben. Zu diesen Beschreibungen zählt zum Beispiel das typischerweise verwendete Währungssymbol und Datumsformat. Eine Locale-Komponente beschreibt auch, welches Zeichen zum Beispiel als Komma in einer Kommazahl verwendet wird und welches Zeichen das Tausendertrennzeichen ist.

Die Locale-Komponente ist insofern für die Verarbeitung von Strings von Bedeutung als dass sie zum Beispiel die in einem Kulturkreis verwendeten Buchstaben und deren Reihenfolge im Alphabet beschreibt. So hängt es vom Kulturkreis ab, ob ein Alphabet zum Beispiel einen Umlaut Ä kennt und an welcher Stelle das Ä im Alphabet steht.

Wenn nun eine Funktion aufgerufen wird, die zum Beispiel einen String in Großbuchstaben konvertieren soll, hängt es wesentlich vom verwendeten Locale ab, wie dies im Einzelnen geschieht. So ist klar, dass im Deutschen ä in Ä konvertiert werden muss. Das muss aber logischerweise nicht auch für andere Kulturkreise und Sprachen gelten.

Wenn Sie mit dem Datentypen std::string arbeiten, mussten Sie sich bisher keine Gedanken über die Locale-Komponente des C++ Standards machen. Denn die Methoden der Klasse std::string arbeiten alle unabhängig von den Eigenschaften eines Kulturkreises. Für die Boost C++ Bibliotheken in diesem Kapitel müssen Sie aber die Locale-Komponente kennen.

Der C++ Standard definiert eine Klasse std::locale in der Headerdatei locale. In jedem C++ Programm gibt es automatisch eine Instanz dieser Klasse, die globales Locale genannt wird. Ein direkter Zugriff auf den globalen Locale ist nicht möglich. Stattdessen muss ein neues Objekt vom Typ std::locale über den Aufruf des Standardkonstruktors erstellt werden, das dann mit den gleichen Eigenschaften des globalen Locales initialisiert wird.

#include <locale> 
#include <iostream> 

int main() 
{ 
  std::locale loc; 
  std::cout << loc.name() << std::endl; 
} 

Obiges Programm gibt C auf die Standardausgabe aus. Dies ist der Name des klassischen Locales. Das klassische Locale enthält Beschreibungen, wie sie standardmäßig in Programmen verwendet werden, die in der Programmiersprache C entwickelt sind. Es handelt sich hierbei um genau das Locale, auf das das globale Locale in einem C++-Programm zu Beginn gesetzt ist.

In jedem C++-Programm wird also standardmäßig als globales Locale das C-Locale verwendet. Dieses Locale enthält Beschreibungen, wie sie im amerikanischen Kulturkreis Verwendung finden. So wird vom C-Locale zum Beispiel als Währungssymbol das Dollarzeichen verwendet. Kommazahlen werden mit einem Punkt als Komma geschrieben, und bei Ausgabe eines Datums wird der Monatsname in Englisch geschrieben.

Wenn Sie das globale Locale ändern wollen, können Sie dies über die statische Methode global() der Klasse std::locale machen.

#include <locale> 
#include <iostream> 

int main() 
{ 
  std::locale::global(std::locale("German")); 
  std::locale loc; 
  std::cout << loc.name() << std::endl; 
} 

Die statische Methode global() erwartet als einzigen Parameter ein neues Objekt vom Typ std::locale. Über einen Konstruktor dieser Klasse, der als einzigen Parameter eine Zeichenkette vom Typ const char* erwartet, kann ein Locale-Objekt für einen Kulturkreis erstellt werden. Die Namen von Locales sind jedoch nicht standardisiert. Einzige Ausnahme ist das C-Locale, das logischerweise den Namen "C" hat. Es hängt daher nun von der verwendeten C++ Standardbibliothek ab, welche Angaben akzeptiert werden. Wenn Sie den Microsoft C++ Compiler in Visual Studio 2008 einsetzen, können Sie laut der Dokumentation der Sprach-Strings über die Angabe "German" die Einstellungen für den deutschen Kulturkreis auswählen.

Wenn Sie das Programm ausführen, erhalten Sie als Ausgabe German_Germany.1252. Die Angabe der Sprache "German" führt also dazu, dass die Einstellungen für Deutsch in Deutschland ausgewählt werden. Außerdem wird die Zeichentabelle 1252 verwendet.

Wenn Sie die Einstellungen für Deutsch in einem anderen Land wie zum Beispiel der Schweiz verwenden möchten, können Sie dies in Visual Studio 2008 wie folgt angeben.

#include <locale> 
#include <iostream> 

int main() 
{ 
  std::locale::global(std::locale("German_Switzerland")); 
  std::locale loc; 
  std::cout << loc.name() << std::endl; 
} 

Obiges Programm gibt German_Switzerland.1252 auf die Standardausgabe aus.

Während Sie nun wissen, was ein Locale ist und wie das globale Locale geändert werden kann, sehen Sie im Folgenden, inwiefern das globale Locale Ergebnisse von String-Funktionen beeinflusst.

#include <locale> 
#include <iostream> 
#include <cstring> 

int main() 
{ 
  std::cout << std::strcoll("ä", "z") << std::endl; 
  std::locale::global(std::locale("German")); 
  std::cout << std::strcoll("ä", "z") << std::endl; 
} 

Im obigen Programm wird die Funktion std::strcoll() aufgerufen, die in der Headerdatei cstring definiert ist. Sie vergleicht zwei Strings und gibt eine Zahl zurück, die angibt, welcher der beiden Strings lexikographisch kleiner ist; oder anders ausgedrückt welcher der beiden Strings im Lexikon vor dem anderen stehen würde.

Wenn Sie obiges Programm ausführen, erhalten Sie als Ergebnis 1 und -1. Obwohl zweimal die gleiche Funktion mit den gleichen Parametern aufgerufen wird, sind die Ergebnisse verschieden. Der Grund ist, dass der erste Aufruf von std::strcoll() den globalen Locale C verwendet, während beim zweiten Aufruf das globale Locale für den deutschen Kulturkreis gesetzt ist. Für diese beiden Locales ist also die Reihenfolge der Zeichen ä und z verschieden.

Zahlreiche C-Funktionen sowieso C++ Streams greifen auf Locales zu. Auch wenn Methoden der Klasse std::string unabhängig von Locales arbeiten, so sind viele der Funktionen, die Sie im Folgenden kennenlernen werden, von Locales abhängig. Deswegen werden Sie in diesem Kapitel noch häufiger auf Locales treffen.


5.3 Boost.StringAlgorithms

Die Boost C++ Bibliothek StringAlgorithms stellt viele freistehende Funktionen im Namensraum boost::algorithm zur Verfügung, mit denen sich Strings verarbeiten lassen. Strings können dabei vom Typ std::string, std::wstring oder einer anderen Instanz der Template-Klasse std::basic_string sein.

Alle Funktionen sind je nach Kategorie in unterschiedlichen Headerdateien definiert. So muss, um auf Funktionen zum Umwandeln von Groß- und Kleinbuchstaben zuzugreifen, die Headerdatei boost/algorithm/string/case_conv.hpp eingebunden werden. Weil Boost.StringAlgorithms jedoch mehr als 20 Kategorien und somit ebenso viele Headerdateien anbietet, steht mit boost/algorithm/string.hpp eine Headerdatei zur Verfügung, die alle anderen einbindet. Diese wird in den nachfolgenden Beispielen verwendet.

Wie bereits im vorhergehenden Abschnitt zu Locales erwähnt, erwarten viele Funktionen von Boost.StringAlgorithms als zusätzlichen Parameter ein Objekt vom Typ std::locale. Dieser zusätzliche Parameter ist jedoch immer optional. Wenn er nicht angegeben wird, wird automatisch das globale Locale verwendet.

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 
#include <clocale> 

int main() 
{ 
  std::setlocale(LC_ALL, "German"); 
  std::string s = "Boris Schäling"; 
  std::cout << boost::algorithm::to_upper_copy(s) << std::endl; 
  std::cout << boost::algorithm::to_upper_copy(s, std::locale("German")) << std::endl; 
} 

Die Funktion boost::algorithm::to_upper_copy() kann verwendet werden, um einen String in Großbuchstaben zu konvertieren. Neben dieser Funktion stellt Boost.StringAlgorithms auch boost::algorithm::to_lower_copy() zur Verfügung, um einen String in Kleinbuchstaben umzuwandeln. Beide Funktionen geben den umgewandelten String als Ergebnis zurück. Soll der String selbst, der als Parameter an die Funktion übergeben wird, umgewandelt werden, müssen boost::algorithm::to_upper() oder boost::algorithm::to_lower() aufgerufen werden.

Im obigen Programm wird der String "Boris Schäling" mit boost::algorithm::to_upper_copy() in Großbuchstaben umgewandelt. Beim ersten Aufruf wird automatisch das globale Locale verwendet, das standardmäßig das C-Locale ist. Beim zweiten Aufruf ist ein Locale für den deutschen Kulturkreis angegeben.

Sie können nur beim Aufruf mit dem Locale für den deutschen Kulturkreis sicher sein, dass die Umwandlung in Großbuchstaben so erfolgt wie Sie es erwarten. Denn nur im deutschen Kulturkreis ist das Ä der passende Großbuchstabe zum ä. Für das C-Locale hingegen ist ä ein unbekanntes Zeichen und wird nicht in Ä umgewandelt. Entweder geben Sie also explizit das entsprechende Locale als Parameter an oder setzen das globale Locale neu, bevor Sie boost::algorithm::to_upper_copy() aufrufen.

Beachten Sie, dass außerdem zu Beginn des Programms die Funktion std::setlocale() aus der Headerdatei clocale aufgerufen wird. Diese Funktion wird verwendet, um den Kulturkreis für C-Funktionen zu setzen. Die Standardausgabe std::cout verwendet intern C-Funktionen, um Daten auf den Bildschirm auszugeben. Indem für C-Funktionen der deutsche Kulturkreis gesetzt wird, werden Umlaute wie ä und Ä richtig auf den Bildschirm ausgegeben.

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{ 
  std::locale::global(std::locale("German")); 
  std::string s = "Boris Schäling"; 
  std::cout << boost::algorithm::to_upper_copy(s) << std::endl; 
  std::cout << boost::algorithm::to_upper_copy(s, std::locale("German")) << std::endl; 
} 

Im obigen Programm ist nun der deutsche Kulturkreis für das globale Locale gesetzt worden. Somit werden auch beim ersten Aufruf von boost::algorithm::to_upper_copy() die entsprechenden Einstellungen des deutschen Kulturkreises verwendet und ä in Ä umgewandelt.

Beachten Sie außerdem, dass in diesem Programm kein Aufruf von std::setlocale() erfolgt. Wenn das globale Locale mit std::locale::global() gesetzt wird, wird gleichzeitig auch das Locale für C-Funktionen gesetzt. In der Praxis rufen Sie in C++-Programmen normalerweise nie std::setlocale() auf, sondern setzen immer das globale Locale mit std::locale::global(). In den nachfolgenden Beispielprogrammen wird dies auch so gehandhabt.

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{ 
  std::locale::global(std::locale("German")); 
  std::string s = "Boris Schäling"; 
  std::cout << boost::algorithm::erase_first_copy(s, "i") << std::endl; 
  std::cout << boost::algorithm::erase_nth_copy(s, "i", 0) << std::endl; 
  std::cout << boost::algorithm::erase_last_copy(s, "i") << std::endl; 
  std::cout << boost::algorithm::erase_all_copy(s, "i") << std::endl; 
  std::cout << boost::algorithm::erase_head_copy(s, 5) << std::endl; 
  std::cout << boost::algorithm::erase_tail_copy(s, 8) << std::endl; 
} 

Boost.StringAlgorithms stellt mehrere Funktionen zur Verfügung, um Zeichen in einem String zu löschen. Dabei kann auf unterschiedliche Weise angegeben werden, welcher Teil genau gelöscht werden soll. So kann zum Beispiel mit boost::algorithm::erase_all_copy() der Buchstabe i aus einem String komplett gelöscht werden oder mit boost::algorithm::erase_first_copy() nur das erste i. Außerdem stehen mit boost::algorithm::erase_head_copy() und boost::algorithm::erase_tail_copy() Funktionen zur Verfügung, die den Anfang oder das Ende eines Strings um eine bestimmte Anzahl an Zeichen kürzen.

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{ 
  std::locale::global(std::locale("German")); 
  std::string s = "Boris Schäling"; 
  boost::iterator_range<std::string::iterator> r = boost::algorithm::find_first(s, "Boris"); 
  std::cout << r << std::endl; 
  r = boost::algorithm::find_first(s, "xyz"); 
  std::cout << r << std::endl; 
} 

Verschiedene Funktionen wie boost::algorithm::find_first() stehen zur Verfügung, um Strings nach anderen Strings zu durchsuchen. So kann auch mit boost::algorithm::find_last(), boost::algorithm::find_nth(), boost::algorithm::find_head() und boost::algorithm::find_tail() nach Strings gesucht werden.

All den genannten Funktionen ist gemeinsam, dass sie ein paar Paar Iteratoren zurückgeben. Dieses Paar hat den Datentyp boost::iterator_range. Diese Klasse stammt aus der Boost C++ Bibliothek Range, die basierend auf dem Iterator-Konzept ein Range-Konzept definiert. Da der Operator << für boost::iterator_range überladen ist, kann das Ergebnis der Suchalgorithmen direkt auf die Standardausgabe ausgegeben werden. So wird im obigen Programm als erstes Ergebnis Boris und als zweites Ergebnis ein leerer String ausgegeben.

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 
#include <vector> 

int main() 
{ 
  std::locale::global(std::locale("German")); 
  std::vector<std::string> v; 
  v.push_back("Boris"); 
  v.push_back("Schäling"); 
  std::cout << boost::algorithm::join(v, " ") << std::endl; 
} 

Der Funktion boost::algorithm::join() wird als erster Parameter ein Container mit Strings übergeben. Die Funktion hängt die Strings im Container aneinander und fügt zwischen zwei Strings jeweils den String ein, der als zweiter Parameter übergeben wird. So gibt obiges Programm Boris Schäling aus.

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{ 
  std::locale::global(std::locale("German")); 
  std::string s = "Boris Schäling"; 
  std::cout << boost::algorithm::replace_first_copy(s, "B", "D") << std::endl; 
  std::cout << boost::algorithm::replace_nth_copy(s, "B", 0, "D") << std::endl; 
  std::cout << boost::algorithm::replace_last_copy(s, "B", "D") << std::endl; 
  std::cout << boost::algorithm::replace_all_copy(s, "B", "D") << std::endl; 
  std::cout << boost::algorithm::replace_head_copy(s, 5, "Doris") << std::endl; 
  std::cout << boost::algorithm::replace_tail_copy(s, 8, "Becker") << std::endl; 
} 

So wie Boost.StringAlgorithms mehrere Funktionen zur Verfügung stellt, um Strings zu durchsuchen oder eine Zeichenkette in Strings zu löschen, stehen auch Funktionen zur Verfügung, um eine Zeichenkette in einem String mit einer anderen Zeichenkette zu ersetzen. Zu diesen Funktionen zählen boost::algorithm::replace_first_copy(), boost::algorithm::replace_nth_copy(), boost::algorithm::replace_last_copy(), boost::algorithm::replace_all_copy(), boost::algorithm::replace_head_copy() und boost::algorithm::replace_tail_copy(). Diese Funktionen werden genauso angewandt wie die Funktionen zum Suchen und Löschen von Zeichenketten. Der einzige Unterschied ist, dass sie einen zusätzlichen Parameter erwarten - nämlich die Zeichenkette, die die gesuchte ersetzen soll.

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{ 
  std::locale::global(std::locale("German")); 
  std::string s = "\t Boris Schäling \t"; 
  std::cout << "." << boost::algorithm::trim_left_copy(s) << "." << std::endl; 
  std::cout << "." <<boost::algorithm::trim_right_copy(s) << "." << std::endl; 
  std::cout << "." <<boost::algorithm::trim_copy(s) << "." << std::endl; 
} 

Wenn eine Zeichenkette an Anfang und Ende automatisch um Leerzeichen gekürzt werden soll, können die Funktionen boost::algorithm::trim_left_copy(), boost::algorithm::trim_right_copy() und boost::algorithm::trim_copy() verwendet werden. Welche Zeichen als Leerzeichen gelten, hängt vom globalen Locale ab.

Für verschiedene Funktionen ermöglicht Boost.StringAlgorithms, als zusätzlichen Parameter ein Predicate zu übergeben, von dem abhängt, auf welche Zeichen in einem String die Funktion angewandt wird. So stehen neben den eben genannten drei Funktionen auch boost::algorithm::trim_left_copy_if(), boost::algorithm::trim_right_copy_if() und boost::algorithm::trim_copy_if() zur Verfügung.

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{ 
  std::locale::global(std::locale("German")); 
  std::string s = "--Boris Schäling--"; 
  std::cout << "." << boost::algorithm::trim_left_copy_if(s, boost::algorithm::is_any_of("-")) << "." << std::endl; 
  std::cout << "." <<boost::algorithm::trim_right_copy_if(s, boost::algorithm::is_any_of("-")) << "." << std::endl; 
  std::cout << "." <<boost::algorithm::trim_copy_if(s, boost::algorithm::is_any_of("-")) << "." << std::endl; 
} 

Im obigen Programm wird zusätzlich auf die Funktion boost::algorithm::is_any_of() zugegriffen. Es handelt sich hierbei um eine Hilfsfunktion, mit der ein Predicate erstellt wird, das überprüft, ob ein Zeichen in dem String vorkommt, der als Parameter an boost::algorithm::is_any_of() übergeben wird. Mit Hilfe von boost::algorithm::is_any_of() kann nun angegeben werden, um welche Zeichen ein String getrimmt werden soll. Im obigen Programm ist das der Bindestrich.

Boost.StringAlgorithms stellt zahlreiche Hilfsfunktionen zur Verfügung, die häufig benötigte Predicates zurückgeben.

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{ 
  std::locale::global(std::locale("German")); 
  std::string s = "123456789Boris Schäling123456789"; 
  std::cout << "." << boost::algorithm::trim_left_copy_if(s, boost::algorithm::is_digit()) << "." << std::endl; 
  std::cout << "." <<boost::algorithm::trim_right_copy_if(s, boost::algorithm::is_digit()) << "." << std::endl; 
  std::cout << "." <<boost::algorithm::trim_copy_if(s, boost::algorithm::is_digit()) << "." << std::endl; 
} 

Das Predicate, das von boost::algorithm::is_digit() zurückgegeben wird, gibt für alle Ziffern true zurück. Neben dieser Funktion stehen auch Hilfsfunktionen zur Verfügung, die Zeichen auf Groß- und Kleinschreibung überprüfen: boost::algorithm::is_upper() und boost::algorithm::is_lower(). All diese Funktionen verwenden automatisch das globale Locale, wenn kein Locale explizit als Parameter übergeben wird.

Neben den Predicates, die mit Hilfe der eben vorgestellten Hilfsfunktionen erstellt werden und die einzelne Zeichen überprüfen, bietet Boost.StringAlgorithms Funktionen an, mit denen Strings überprüft werden können.

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{ 
  std::locale::global(std::locale("German")); 
  std::string s = "Boris Schäling"; 
  std::cout << boost::algorithm::starts_with(s, "Boris") << std::endl; 
  std::cout << boost::algorithm::ends_with(s, "Schäling") << std::endl; 
  std::cout << boost::algorithm::contains(s, "is") << std::endl; 
  std::cout << boost::algorithm::lexicographical_compare(s, "Boris") << std::endl; 
} 

Die Funktionen boost::algorithm::starts_with(), boost::algorithm::ends_with(), boost::algorithm::contains() und boost::algorithm::lexicographical_compare() können aufgerufen werden, um zwei Strings miteinander zu vergleichen.

Zuguterletzt wird Ihnen eine Funktion vorgestellt, mit denen sich ein String in mehrere Bestandteile zerlegen lässt.

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 
#include <vector> 

int main() 
{ 
  std::locale::global(std::locale("German")); 
  std::string s = "Boris Schäling"; 
  std::vector<std::string> v; 
  boost::algorithm::split(v, s, boost::algorithm::is_space()); 
  std::cout << v.size() << std::endl; 
} 

Die Funktion boost::algorithm::split() wird verwendet, um einen String an bestimmten Stellen in seine Teile zu zerlegen und diese in einem Container zu speichern. Der Funktion muss als dritter Parameter ein Predicate übergeben werden, das für jedes Zeichen entscheidet, ob der String an dieser Stelle geteilt werden soll oder nicht. Hier können die von Boost.StringAlgorithms zur Verfügung gestellten Hilfsfunktionen aufgerufen werden, die Predicates zurückgeben. Im obigen Programm wird auf boost::algorithm::is_space() zugegriffen, so dass der String an Leerzeichen gesplittet wird.

Viele der Funktionen, die Sie in diesem Abschnitt kennengelernt haben, existieren auch in einer Variante, die Groß- und Kleinschreibung ignoriert. Diese Funktionen haben typischerweise den gleichen Namen, beginnen aber mit einem i. So heißt die zu boost::algorithm::erase_all_copy() entsprechende Funktion, die Zeichenketten unabhängig von der Groß- und Kleinschreibung löscht, boost::algorithm::ierase_all_copy().

Abschließend soll erwähnt werden, dass Boost.StringAlgorithms für mehrere Funktionen auch reguläre Ausdrücke unterstützt. So kann zum Beispiel mit boost::algorithm::find_regex() nach einem regulären Ausdruck gesucht werden.

#include <boost/algorithm/string.hpp> 
#include <boost/algorithm/string/regex.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{ 
  std::locale::global(std::locale("German")); 
  std::string s = "Boris Schäling"; 
  boost::iterator_range<std::string::iterator> r = boost::algorithm::find_regex(s, boost::regex("\\w\\s\\w")); 
  std::cout << r << std::endl; 
} 

Wie Sie sehen muss für den regulären Ausdruck auf eine Klasse boost::regex zugegriffen werden, die in der Boost C++ Bibliothek Regex definiert ist. Diese Bibliothek wird Ihnen im Folgenden vorgestellt.


5.4 Boost.Regex

Die Boost C++ Bibliothek Regex ermöglicht es, reguläre Ausdrücke in C++ anzuwenden. Reguläre Ausdrücke sind ein mächtiges Instrument, das die Suche in Texten erleichtert und von vielen Programmiersprachen unterstützt wird.

Wenn Sie Zugriff auf eine aktuelle C++ Standardbibliothek haben, können Sie wohlmöglich reguläre Ausdrücke ohne Boost.Regex verwenden. Im Technical Report 1 wurde Boost.Regex in den C++ Standard aufgenommen, so dass Sie die Klassen und Funktionen, die Sie im Folgenden kennenlernen werden, auch im Namensraum std finden.

Die wichtigsten Klassen in Boost.Regex sind boost::regex und boost::smatch, die beide in der Headerdatei boost/regex.hpp definiert sind. Während boost::regex verwendet wird, um einen regulären Ausdruck zu definieren, speichert boost::smatch Suchergebnisse.

Die drei Funktionen, die Boost.Regex zur Suche mit regulären Ausdrücken anbietet, lernen Sie im Folgenden kennen.

#include <boost/regex.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{ 
  std::locale::global(std::locale("German")); 
  std::string s = "Boris Schäling"; 
  boost::regex expr("\\w+\\s\\w+"); 
  std::cout << boost::regex_match(s, expr) << std::endl; 
} 

Die Funktion boost::regex_match() wird verwendet, um einen String komplett mit einem regulären Ausdruck zu vergleichen. Die Funktion gibt nur dann true zurück, wenn der reguläre Ausdruck auf den gesamten String passt.

Um einen String nach einem regulären Ausdruck zu durchsuchen, wird boost::regex_search() verwendet.

#include <boost/regex.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{ 
  std::locale::global(std::locale("German")); 
  std::string s = "Boris Schäling"; 
  boost::regex expr("(\\w+)\\s(\\w+)"); 
  boost::smatch what; 
  if (boost::regex_search(s, what, expr)) 
  { 
    std::cout << what[0] << std::endl; 
    std::cout << what[1] << " " << what[2] << std::endl; 
  } 
} 

Die Funktion boost::regex_search() erwartet als zusätzlichen Parameter eine Referenz auf ein Objekt vom Typ boost::smatch. In diesem Objekt speichert boost::regex_search() die Ergebnisse. Dabei wird nur nach Gruppierungen gesucht. So werden im obigen Programm zwei Ergebnisse gespeichert, weil es zwei Gruppierungen im regulären Ausdruck gibt.

Die Klasse boost::smatch, in der Suchergebnisse gespeichet werden, ist ein Container für Elemente vom Typ boost::sub_match. Sie bietet eine ähnliche Schnittstelle wie std::vector. So kann über operator[]() auf Elemente vom Typ boost::sub_match zugegriffen werden.

Die Klasse boost::sub_match wiederum speichert Iteratoren auf die Positionen in einem String, die einer Gruppierung in einem regulären Ausdruck entsprechen. Da boost::sub_match von std::pair abgeleitet ist, kann über first und second auf die Iteratoren zugegriffen werden, die einen Substring identifizieren. Wie im obigen Programm zu sehen muss aber nicht auf diese Iteratoren zugegriffen werden, um einen Substring auf die Standardausgabe auszugeben. Da der Operator << für boost::sub_match überladen ist, kann ein Substring direkt ausgegeben werden.

Beachten Sie, dass boost::sub_match Iteratoren speichert. Ergebnisse werden also nicht kopiert. Sie dürfen daher auf Iteratoren in Objekten vom Typ boost::sub_match nur zugreifen, solange der String, auf den die Iteratoren zeigen, existiert.

Beachten Sie außerdem, dass das erste Element im Container boost::smatch mit dem Index 0 Iteratoren speichert, die auf den String zeigen, der dem gesamten regulären Ausdruck entspricht. Der erste Substring, der der ersten Gruppierung entspricht, wird über den Index 1 angesprochen.

Die dritte Funktion, die Boost.Regex anbietet, ist boost::regex_replace().

#include <boost/regex.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{ 
  std::locale::global(std::locale("German")); 
  std::string s = " Boris Schäling "; 
  boost::regex expr("\\s"); 
  std::string fmt("_"); 
  std::cout << boost::regex_replace(s, expr, fmt) << std::endl; 
} 

Der Funktion boost::regex_replace() muss neben dem zu durchsuchenden String und einem regulären Ausdruck ein Format übergeben werden. Das Format definiert, wie Substrings, die Gruppierungen im regulären Ausdruck entsprechen, genau ersetzt werden. Wenn der reguläre Ausdruck so wie oben keine Gruppierungen enthält, werden entsprechende Substrings eins zu eins mit dem Format ersetzt. Für das obige Programm bedeutet dies, dass es als Ergebnis _Boris_Schäling_ ausgibt.

Beachten Sie, dass boost::regex_replace() den gesamten String nach einem regulären Ausdruck durchsucht. So werden im obigen Programm alle drei Leerzeichen durch einen Unterstrich ersetzt.

#include <boost/regex.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{ 
  std::locale::global(std::locale("German")); 
  std::string s = "Boris Schäling"; 
  boost::regex expr("(\\w+)\\s(\\w+)"); 
  std::string fmt("\\2 \\1"); 
  std::cout << boost::regex_replace(s, expr, fmt) << std::endl; 
} 

Im Format kann auf Substrings zugegriffen werden, die durch Gruppierungen im regulären Ausdruck zurückgegeben werden. So können wie im obigen Beispiel die Positionen des Vor- und Nachnamen getauscht werden, so dass als Ergebnis Schäling Boris ausgegeben wird.

Beachten Sie, dass es verschiedene Standards für reguläre Ausdrücke und Formate gibt. So kann für alle drei vorgestellten Funktionen ein weiterer Parameter übergeben werden, mit dem ein bestimmter Standard ausgewählt werden kann. Außerdem kann zum Beispiel angegeben werden, dass Sonderzeichen in einem Format nicht interpretiert werden sollen, sondern das Format komplett den String ersetzen soll, auf den der reguläre Ausdruck zutrifft.

#include <boost/regex.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{ 
  std::locale::global(std::locale("German")); 
  std::string s = "Boris Schäling"; 
  boost::regex expr("(\\w+)\\s(\\w+)"); 
  std::string fmt("\\2 \\1"); 
  std::cout << boost::regex_replace(s, expr, fmt, boost::regex_constants::format_literal) << std::endl; 
} 

Im obigen Programm wird das Flag boost::regex_constants::format_literal als vierter Parameter an boost::regex_replace() übergeben, um die Interpretation der Sonderzeichen im Format zu unterdrücken. Das Programm gibt als Ergebnis daher \2 \1 aus, da der gesamte String, auf den der reguläre Ausdruck zutrifft, mit dem Format ersetzt wird.

Wie am Ende des vorherigen Abschnitts erwähnt können reguläre Ausdrücke auch mit Boost.StringAlgorithms verwendet werden. Diese Bibliothek greift dabei auf Boost.Regex zu, um unter anderem Funktionen wie boost::algorithm::find_regex(), boost::algorithm::replace_regex(), boost::algorithm::erase_regex() und boost::algorithm::split_regex() anzubieten. Da wie zu Beginn dieses Abschnitts erwähnt Boost.Regex jedoch im Technical Report 1 in den C++ Standard aufgenommen wurde, ist es empfehlenswert, die Anwendung regulärer Ausdrücke auch ohne Boost.StringAlgorithms zu erlernen und beherrschen.


5.5 Boost.Tokenizer

Die Bibliothek Boost.Tokenizer ermöglicht es, über Teilausdrücke in einem String zu iterieren. Dabei werden bestimmte Zeichen im String als Trennzeichen interpretiert. Sehen Sie sich dazu folgendes Beispiel an.

#include <boost/tokenizer.hpp> 
#include <string> 
#include <iostream> 

int main() 
{ 
  typedef boost::tokenizer<boost::char_separator<char> > tokenizer; 
  std::string s = "Boost C++ libraries"; 
  tokenizer tok(s); 
  for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) 
    std::cout << *it << std::endl; 
} 

Boost.Tokenizer definiert eine Klasse boost::tokenizer in der Headerdatei boost/tokenizer.hpp. Bei dieser Klasse handelt es sich um ein Template: Als Template-Parameter muss eine Klasse angegeben werden, die zusammenhängende Ausdrücke identifiziert. So wird im obigen Beispiel die Klasse boost::char_separator verwendet, die Leerzeichen und Interpunktionszeichen als Trennzeichen interpretiert.

Ein Tokenizer muss mit einem String vom Typ std::string initialisiert werden. Über die Methoden begin() und end() kann der Tokenizer wie ein Container behandelt werden und über einen Iterator auf Teilausdrücke des Strings zugegriffen werden, mit dem der Tokenizer initialisiert wurde. Wie Teilausdrücke gefunden werden, hängt wie bereits erwähnt von der Klasse ab, die als Template-Parameter angegeben ist.

Obiges Programm gibt Boost, C, +, + und libraries aus. Das liegt daran, dass boost::char_separator standardmäßig Leerzeichen und Interpunktionszeichen als Trennzeichen interpretiert. Um diese Zeichen identifiziereren zu können, greift boost::char_separator auf die Funktionen std::isspace() und std::ispunct() zu. Warum die beiden Pluszeichen ausgegeben werden, liegt daran, dass Boost.Tokenizer zwischen Trennzeichen unterscheidet, die unterdrückt und die ausgegeben werden soll: Standardmäßig werden Leerzeichen unterdrückt und Interpunktionszeichen ausgegeben.

Für den Fall, dass Interpunktionszeichen nicht als Trennzeichen interpretiert werden sollen, können Sie ein Objekt vom Typ boost::char_separator entsprechend initialisieren und als Parameter an den Tokenizer übergeben. Sehen Sie sich dazu folgendes Beispiel an.

#include <boost/tokenizer.hpp> 
#include <string> 
#include <iostream> 

int main() 
{ 
  typedef boost::tokenizer<boost::char_separator<char> > tokenizer; 
  std::string s = "Boost C++ libraries"; 
  boost::char_separator<char> sep(" "); 
  tokenizer tok(s, sep); 
  for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) 
    std::cout << *it << std::endl; 
} 

Der Konstruktor von boost::char_separator erwartet drei Parameter, von denen lediglich der erste angegeben werden muss. Der erste Parameter beschreibt dabei alle Trennzeichen, die unterdrückt werden sollen. Für obiges Beispiel bedeutet das wie zuvor, dass Leerzeichen Trennzeichen sind.

Der zweite Parameter beschreibt Trennzeichen, die ausgegeben werden sollen. Wird der zweite Parameter so wie im obigen Beispiel nicht angegeben, ist er leer. Es gibt demnach keine Trennzeichen, die ausgegeben werden sollen. Wird obiges Programm ausgeführt, wird daher nun Boost, C++ und libraries ausgegeben.

Wenn Sie als zweiten Parameter ein Pluszeichen angeben, verhält sich das folgende Programm wie das erste Beispiel zu Boost.Tokenizer.

#include <boost/tokenizer.hpp> 
#include <string> 
#include <iostream> 

int main() 
{ 
  typedef boost::tokenizer<boost::char_separator<char> > tokenizer; 
  std::string s = "Boost C++ libraries"; 
  boost::char_separator<char> sep(" ", "+"); 
  tokenizer tok(s, sep); 
  for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) 
    std::cout << *it << std::endl; 
} 

Der dritte Parameter beschreibt, ob leere Teilausdrücke ausgegeben werden sollen oder nicht. Wenn zwei Trennzeichen direkt hintereinander stehen, ist der dazwischenliegende Teilausdruck leer. Standardmäßig werden diese leeren Teilausdrücke nicht ausgegeben. Sie können jedoch über den dritten Parameter das Verhalten von boost::char_separator ändern.

#include <boost/tokenizer.hpp> 
#include <string> 
#include <iostream> 

int main() 
{ 
  typedef boost::tokenizer<boost::char_separator<char> > tokenizer; 
  std::string s = "Boost C++ libraries"; 
  boost::char_separator<char> sep(" ", "+", boost::keep_empty_tokens); 
  tokenizer tok(s, sep); 
  for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) 
    std::cout << *it << std::endl; 
} 

Wenn Sie obiges Programm ausführen, werden zusätzlich zwei leere Teilausdrücke ausgegeben. So befindet sich ein leerer Teilausdruck zwischen den beiden Pluszeichen und zwischen dem zweiten Pluszeichen und dem folgenden Leerzeichen.

Es ist auch möglich, den Tokenizer mit anderen Stringtypen zu verwenden.

#include <boost/tokenizer.hpp> 
#include <string> 
#include <iostream> 

int main() 
{ 
  typedef boost::tokenizer<boost::char_separator<wchar_t>, std::wstring::const_iterator, std::wstring> tokenizer; 
  std::wstring s = L"Boost C++ libraries"; 
  boost::char_separator<wchar_t> sep(L" "); 
  tokenizer tok(s, sep); 
  for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) 
    std::wcout << *it << std::endl; 
} 

Im obigen Beispiel wird über einen String vom Typ std::wstring iteriert. Damit der Tokenizer einen String von diesem Typ als Parameter akzeptiert, ist es notwendig, zusätzliche Template-Parameter anzugeben. Auch der Klasse boost::char_separator muss in diesem Fall als Template-Parameter wchar_t übergeben werden.

Neben boost::char_separator bietet Boost.Tokenizer zwei weitere Klassen an, um Teilausdrücke zu identifizieren.

#include <boost/tokenizer.hpp> 
#include <string> 
#include <iostream> 

int main() 
{ 
  typedef boost::tokenizer<boost::escaped_list_separator<char> > tokenizer; 
  std::string s = "Boost,\"C++ libraries\""; 
  tokenizer tok(s); 
  for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) 
    std::cout << *it << std::endl; 
} 

Die Klasse boost::escaped_list_separator kann verwendet werden, um mehrere Werte zu lesen, die durch Komma getrennt sind. Dieses Format wird als CSV bezeichnet, was für comma separated value steht. Dabei werden von boost::escaped_list_separator auch Anführungszeichen und sogenannte Escape-Zeichen berücksichtigt. So gibt obiges Programm Boost und C++ libraries aus.

Eine weitere von Boost.Tokenizer zur Verfügung gestellte Klasse ist boost::offset_separator. Diese Klasse muss instantiiert werden. Das entsprechende Objekt muss als zweiter Parameter an den Konstruktor von boost::tokenizer übergeben werden.

#include <boost/tokenizer.hpp> 
#include <string> 
#include <iostream> 

int main() 
{ 
  typedef boost::tokenizer<boost::offset_separator> tokenizer; 
  std::string s = "Boost C++ libraries"; 
  int offsets[] = { 5, 5, 9 }; 
  boost::offset_separator sep(offsets, offsets + 3); 
  tokenizer tok(s, sep); 
  for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) 
    std::cout << *it << std::endl; 
} 

Mit boost::offset_separator werden die Stellen im String beschrieben, an denen Teilausdrücke enden. So gibt obiges Programm an, dass der erste Teilausdruck nach 5 Zeichen, der zweite nach weiteren 5 Zeichen und der dritte nach weiteren 6 Zeichen endet. Das Programm gibt also Boost,  C++  und libraries aus.


5.6 Boost.Format

Boost.Format bietet einen Ersatz für die Funktion std::printf() aus der Headerdatei cstdio. Diese Funktion entstammt dem C Standard und ermöglicht eine formatierte Datenausgabe. Sie ist jedoch weder typsicher noch erweiterbar. In C++-Programmen ist daher Boost.Format eigentlich immer vorzuziehen, wenn Daten formatiert ausgegeben werden sollen.

Die Bibliothek Boost.Format bietet eine Klasse boost::format an, die in der Headerdatei boost/format.hpp definiert ist. Dem Konstruktor dieser Klasse wird ähnlich wie std::printf() ein String übergeben, der Sonderzeichen zur Formatierung enthält. Daten, die die Sonderzeichen ersetzen sollen, werden mit dem Operator % verknüpft. Im Folgenden sehen Sie ein Beispiel.

#include <boost/format.hpp> 
#include <iostream> 

int main() 
{ 
  std::cout << boost::format("%1%.%2%.%3%") % 16 % 9 % 2008 << std::endl; 
} 

Als Platzhalter werden von Boost.Format Zahlen verwendet, die zwischen zwei Prozentzeichen stehen. Mit diesen Zahlen wird auf Daten verwiesen, die mit dem Operator % verknüpft werden müssen. So werden im obigen Programm die Zahlen 16, 9 und 2008 zum Datum 16.9.2008 verknüpft. Wenn wie in den USA der Monat vor dem Tag stehen soll, können anstatt den Zahlen die Platzhalter ausgetauscht werden.

#include <boost/format.hpp> 
#include <iostream> 

int main() 
{ 
  std::cout << boost::format("%2%/%1%/%3%") % 16 % 9 % 2008 << std::endl; 
} 

Obiges Programm gibt als Ergebnis 9/16/2008 aus.

Um Daten zu formatieren, können mit Hilfe der Funktion boost::io::group(), die ebenfalls von Boost.Format zur Verfügung gestellt wird, die aus dem C++ Standard bekannten Manipulatoren angewandt werden.

#include <boost/format.hpp> 
#include <iostream> 

int main() 
{ 
  std::cout << boost::format("%1% %2% %1%") % boost::io::group(std::showpos, 99) % 100 << std::endl; 
} 

Das obige Programm gibt als Ergebnis +99 100 +99 aus. Weil der Manipulator std::showpos() mit boost::io::group() auf die Zahl 99 angewandt wird, wird das Pluszeichen immer dann mitangegeben, wenn 99 ausgegeben werden soll.

Soll nur die erste Ausgabe von 99 ein Pluszeichen mitanzeigen, muss das Sonderzeichen zur Formatierung angepasst werden.

#include <boost/format.hpp> 
#include <iostream> 

int main() 
{ 
  std::cout << boost::format("%|1$+| %2% %1%") % 99 % 100 << std::endl; 
} 

Der Platzhalter %1% wird nun durch %|1$+| ersetzt. Wenn die Formatierung angepasst werden soll, erfordert dies nicht nur zwei zusätzliche vertikale Balken. Der Verweis auf eine Variable muss ebenfalls zwischen die vertikalen Balken gesetzt werden und erfolgt nicht mehr mit 1%, sondern mit 1$. Das alles ist notwendig, um das Pluszeichen unterzubringen, damit dann als Ergebnis +99 100 99 erhalten wird.

Beachten Sie, dass Verweise auf Variablen optional sind. Sie müssen sich aber für alle Platzhalter entscheiden, ob Sie Verweise angeben wollen oder nicht. So ist im folgenden Beispiel für den ersten Platzhalter kein Verweis angegeben, dafür aber für den zweiten und dritten, was bei der Ausführung zu einem Fehler führt.

#include <boost/format.hpp> 
#include <iostream> 

int main() 
{ 
  try 
  { 
    std::cout << boost::format("%|+| %2% %1%") % 99 % 100 << std::endl; 
  } 
  catch (boost::io::format_error &ex) 
  { 
    std::cout << ex.what() << std::endl; 
  } 
} 

Im obigen Programm wird eine Ausnahme vom Typ boost::io::format_error geworfen. Genaugenommen wird von Boost.Format boost::io::bad_format_string geworfen. Da die verschiedenen Klassen aber von boost::io::format_error abgeleitet sind, ist es einfacher, Ausnahmen von diesem Typ zu fangen.

Obiges Programm ohne Verweise auf Variablen muss wie folgt geschrieben werden.

#include <boost/format.hpp> 
#include <iostream> 

int main() 
{ 
  std::cout << boost::format("%|+| %|| %||") % 99 % 100 % 99 << std::endl; 
} 

Wenn Sie auf die Angabe der vertikalen Balken, die in diesem Fall für den zweiten und dritten Platzhalter die Formatierung gar nicht näher spezifieren, verzichten möchte, können Sie dies tun. Sie erhalten dann eine Syntax, die der von std::printf() sehr ähnlich ist.

#include <boost/format.hpp> 
#include <iostream> 

int main() 
{ 
  std::cout << boost::format("%+d %d %d") % 99 % 100 % 99 << std::endl; 
} 

Beachten Sie, dass die Formatierung zwar der von std::printf() sehr ähnlich sieht, Boost.Format aber dennoch den Vorteil der Typsicherheit bietet. Denn der Buchstabe d in der Formatierung bedeutet nicht, dass eine Dezimalzahl ausgegeben werden muss, sondern dass der Manipulator std::dec() auf den Stream angewandt wird, den boost::format intern verwendet. So können Formatierungen angegeben werden, die für std::printf() keinen Sinn machen und bei std::printf() zu einem Programmabsturz führen könnten.

#include <boost/format.hpp> 
#include <iostream> 

int main() 
{ 
  std::cout << boost::format("%+s %s %s") % 99 % 100 % 99 << std::endl; 
} 

Während für std::printf() der Buchstabe s lediglich für Strings verwendet werden kann - also den Datentypen const char* in C - funktioniert obiges Programm einwandfrei. Denn Boost.Format erwartet hier nicht zwingend Strings, sondern setzt lediglich entsprechende Manipulatoren ein, um die Funktionsweise des internen Streams zu konfigurieren. Aber auch dann ist es selbstverständlich möglich, die Zahlen, wie sie im obigen Programm übergeben werden, dem internen Stream hinzuzufügen.


5.7 Aufgaben

Sie können die Lösungen zu allen Aufgaben in diesem Buch als ZIP-Datei erwerben.

  1. Erstellen Sie ein Programm, das Vor- und Nachnamen, Geburtstag und Kontostand aus einem XML-String wie dem Folgenden extrahiert und die Daten nacheinander auf den Bildschirm ausgibt: <person><name>Karl-Heinz Huber</name><dob>1970-9-30</dob><account>2900.64 EUR</account></person>.

    Der Vor- und Nachname sollen getrennt voneinander ausgegeben werden. Außerdem soll das Geburtsdatum im üblicherweise verwendeten Format tag.monat.jahr angezeigt werden. Der Kontostand wiederum soll ohne Nachkommastellen ausgegeben werden. Testen Sie Ihr Programm mit weiteren XML-Strings, in denen Sie zum Beispiel an unterschiedlichen Stellen zusätzliche Leerzeichen setzen, einen zweiten Vornamen hinzufügen, den Kontostand auf eine negative Zahl setzen etc.

  2. Erstellen Sie ein Programm, dass Datensätze wie den Folgenden formatiert auf den Bildschirm ausgibt: München Hamburg 92.12 8:25 9:45. In diesem Datensatz wird ein Flug von München nach Hamburg für 92,12 Euro beschrieben, der um 8:25 Uhr abhebt und um 9:45 Uhr landet. Dieser Datensatz soll wie folgt auf den Bildschirm ausgegeben werden: München    -> Hamburg      92.12 EUR (08:25-09:45).

    Im Detail bedeutet dies, dass für Ortsnamen genau zehn Stellen zur Verfügung stehen und Ortsnamen links ausgerichtet werden sollen. Für den Preis stehen sieben Stellen zur Verfügung; er soll rechts ausgerichtet werden. Hinter dem Preis soll außerdem die Währung angegeben werden. Die Abflugs- und Ankunftszeiten sollen ohne Leerzeichen in Klammern und getrennt durch einen Bindestrich ausgegeben werden. Für Abflugs- und Ankunftszeiten vor 10 Uhr soll zusätzlich eine 0 vorangestellt werden. Testen Sie Ihr Programm anschließend mit weiteren Datensätzen, indem Sie zum Beispiel einen Ortsnamen wie Thessaloniki verwenden, der aus mehr als zehn Zeichen besteht.