30 Nationale Besonderheiten |
Dieses Kapitel behandelt die folgenden Themen:
Länderspezifische Zeichenformate einstellen
Zeichensätze und -codierung
Geld-, Datums- und Zahlenformate
Konstruktion eines eigenen Formats
Die Klasse locale (Header <locale>) bestimmt die nationalen Besonderheiten von Zeichensätzen. Dazu gehören Sonderzeichen wie die deutschen Umlaute oder Zeichen mit Akzent wie in den Worten señor und garçon. Die Ordnung zum Vergleich von Zeichenketten wird dadurch definiert, das heißt zum Beispiel, ob ä unter a oder unter ae einsortiert wird, und das Erscheinungsbild für die Ein- und Ausgabe numerischer Größen (Dezimalpunkt oder -komma?) und Datumsangaben (31.1.2015 oder 1/31/2015). Die verschiedenen Kategorien werden in Facetten (englisch facets) unterteilt. Dieses Kapitel konzentriert sich auf die häufigsten Anwendungen. Es ist möglich, eigene Facetten zu schreiben, die von den vorhandenen abgeleitet sind. Einzelheiten sind [ISOC++] zu entnehmen.
30.1 | Sprachumgebung festlegen und ändern |
Ein locale-Objekt wird von der Iostream-Bibliothek benutzt, damit die üblichen nationalen Gepflogenheiten bei der Ein- und Ausgabe eingehalten werden. Wenn von der deutschen Schreibweise von Zahlen auf die angloamerikanische umgeschaltet werden soll, wird das locale-Objekt des Ein- oder Ausgabestroms entsprechend ausgewechselt. Das geschieht mit der Funktion imbue(), der als Parameter ein locale-Objekt übergeben wird. Das englische Wort imbue bedeutet etwa »erfüllen (mit)« oder »inspirieren (mit)«.
using namespace std; // auch locale ist in std locale eineSprachumgebung("POSIX"); // Sprachumgebung definieren cin.imbue(eineSprachumgebung); // cin auf diese Sprachumgebung setzen locale deutsch("de_DE"); // Sprachumgebung definieren // global() setzt eine Sprachumgebung global und gibt die vorherige globale // Sprachumgebung zurück: locale vorherigeSprachumgebung = locale::global(deutsch); cout.imbue(locale("de_DE")); // Ausgabe deutsch formatieren // ... locale::global(vorherigeSprachumgebung)); // globale Sprachumgebung zurücksetzen // (Rückgabewert wird hier ignoriert)
Das Setzen der globalen Sprachumgebung, die normalerweise durch Abfrage der Betriebssystemumgebungsvariablen LANG voreingestellt wird, wirkt sich nicht auf existierende Streams wie cin oder cout aus, nur auf neu erzeugte – deswegen muss ggf. imbue() angewendet werden. Falls LANG nicht definiert ist, wird automatisch die C-Sprachumgebung gesetzt, auch classic genannt (siehe Beispiel unten). POSIX (= Portable Operating System Interface for uniX) ist eine Familie von Standards für Betriebssystemschnittstellen. Anstatt POSIX kann eine von vielen anderen Umgebungen gewählt werden, von denen einige hier aufgelistet sind:
de_DE |
= Deutsch für Deutschland (ISO 8859-1) |
de_DE@euro |
= Deutsch für Deutschland mit Euro-Zeichen (ISO 8859-15) |
de_DE.utf8 |
= Deutsch für Deutschland (Unicode UTF-8) |
de_CH |
= Deutsch für die Schweiz |
en_GB |
= Englisch für Großbritannien |
en_US |
= amerikanisches Englisch |
es_SV |
= Spanisch für El Salvador |
Auf Linux- und macOS-Systemen kann das eingestellte Locale mit locale angezeigt werden. locale -a listet alle verfügbaren Locales auf.
setzt die Standardausgabe auf die C-Sprachumgebung zurück.
Hinweis
Im Folgenden werden Objekte des Typs locale benutzt, um zum Beispiel eine deutsche Schreibweise für ein Datum einzustellen. Leider sind die Konventionen für die Namensgebung systemabhängig. Die g++-Compiler für Linux und macOS unterstützen diverse Sprachen, ihr Gegenstück für Windows, der MinGW-C++-Compiler, leider nicht! Es gibt also eine Fehlermeldung bei der Ausführung. MinGW kann leider nur die Umgebungen "C" und "". Um festzustellen, welche Locales (wie de_DE und en_US) unterstützt werden, können Sie das folgende Programm checklocales.cpp abwandeln und nutzen.
#include <array> #include <iostream> #include <locale> #include <stdexcept> using namespace std; int main() { locale env(""); cout << "aktuell˽eingestellte˽Locale:˽" << env.name() << ’\n’; constexpr array locales { // alle Systeme: "C", // classic C "", // eingestellte Umgebung // manche Systeme // Deutsch, Deutsch UTF, am. Englisch UTF, Spanisch "de_DE", "de_DE.utf8", "de_DE.iso88591", "en_US.utf8", "es_ES", "falsch" }; cout << "\nTest˽der˽locale-Unterstuetzung˽fuer˽C++:\n\n"; for (auto locstring : locales) { cout << "locale˽\"" << locstring << "\"˽ist˽diesem˽C++-Compiler˽"; try { locale loc(locstring); cout << "bekannt.\n"; } catch (const runtime_error& e) { cout << "NICHT˽bekannt." << e.what() << ’\n’; } } }
30.1.1 | Die locale-Funktionen |
locale()
Konstruktor, der eine Kopie des aktuellen globalen locale-Objekts (gegebenenfalls mit global() eingestellt, siehe oben) erzeugt
explicit locale(const char* name)
explicit locale(const string& name) Konstruktor. Das übergebene Argument kann zum Beispiel "de_DE.utf8" sein.
locale(const locale& other, const char* name, category cat)
locale(const locale& other, string name, category cat)
Der Konstruktor kopiert other mit Ausnahme der Facetten, die in cat definiert sind. cat kann zum Beispiel (monetary | numeric) sein. Sie werden entsprechend name gewählt. Zum Typ category siehe Abschnitt 30.4.
locale(const locale& loc1, const locale& loc2, category cats)
konstruiert ein locale-Objekt mit allen Facetten aus loc1 mit Ausnahme derjenigen, die cats implementieren und welche stattdessen aus loc2 genommen werden.
template<class Facet> locale(const locale& other, Facet *f)
Der Konstruktor kopiert alle Facetten aus other mit Ausnahme derjenigen des Typs Facet und installiert *f als Facette, falls f ungleich null ist. Falls f gleich null ist, ist das erzeugte Objekt eine Kopie von other.
const locale& operator=(const locale& rechts) Zuweisungsoperator
bool operator==(const locale& other) const Gleichheitsoperator
template<class Facet> locale combine(const locale& other)
gibt eine Kopie von *this zurück, wobei aber die Facette Facet durch die entsprechende von other ersetzt wird.
string name() const
gibt den Namen des locale-Objekts zurück, falls definiert. Andernfalls wird »*« zurückgegeben.
bool operator()(const string& s1, const string& s2) const
gibt s1 < s2 zurück. Damit kann leicht zum Beispiel ein Vektor v entsprechend den nationalen Zeichenvergleichsregeln, die in einem locale-Objekt loc festgelegt sind, sortiert werden (Beispiel siehe Seite 719).
static locale global(const locale& loc)
setzt das globale locale-Objekt. Der vorherige Wert wird zurückgegeben.
static const locale& classic()
gibt ein locale-Objekt für die C-Sprachumgebung zurück (entspricht locale("C")).
template<class Facet> const Facet& use_facet(const locale& loc)
gibt die Referenz der Facette des Typs Facet des locale-Objekts loc zurück. Falls eine Facette dieses Typs in loc nicht existiert, wird eine bad_cast-Exception ausgeworfen.
template<class Facet> bool has_facet(const locale& loc)
gibt zurück, ob eine Facette des Typs Facet in loc existiert.
30.2 | Zeichensätze und -codierung |
Ein Zeichensatz ist eine Abbildung von Bitmustern auf Zeichen. Damit Sender und Empfänger sich beim elektronischen Datenaustausch verstehen, ist eine Konvention über die Bedeutung der Bitmuster unerlässlich. Die bekannteste ist ASCII (American Standard Code for Information Interchange). Die ASCII-Tabelle definiert die ersten 7 Bits1 eines Bytes, also 128 Zeichen (siehe Anhang A.1). Umlaute und andere europäische Sonderzeichen sind nicht enthalten, sodass nach und nach viele weitere Zeichensätze hinzukamen. Sehr bekannt ist ISO 8859-1, auch »Latin 1« genannt. Er stimmt in den ersten 128 Bytes mit ASCII überein und definiert in den anderen 128 Bytes Umlaute wie ä, ö, ü und weitere Sonderzeichen, sodass ISO 8859-1 mehr als 20 Sprachen abdeckt. ISO 8859-15, auch »Latin 9« genannt, ist eine Modifikation, die auch das Euro-Zeichen e enthält.
ISO 8859-1 hat den großen Vorteil, dass für die Darstellung eines Zeichens ein Byte ausreichend ist. Wenn allerdings noch arabische, chinesische, japanische und koreanische Zeichen gefragt sind, reicht ein Byte nicht. Aus diesem Grund wurde Unicode [Unic] entwickelt (siehe auch UTF im Glossar, Seite 985). Unicode hat den Anspruch, jedem Zeichen eine Codierung zuzuordnen. Wegen der Fülle der möglichen Zeichen müssen viele Zeichen als Multi-Byte-Sequenzen abgebildet werden. Es gibt mehrere Unicode-Schemata; das am weitesten verbreitete ist UTF-8 (8-Bit Unicode Transformation Format). Jedes UTF-8-Byte hat diese Eigenschaften:
Falls das höchste Bit eines Bytes 0 ist, handelt es sich bei dem Byte um ein ASCII-Zeichen. Die anderen 7 Bits definieren den ASCII-Wert. Die ersten 128 UTF-8-Zeichen stimmen daher mit dem ASCII überein.
Falls das höchste Bit 1 ist, ist das Byte Teil einer Multi-Byte-Sequenz.
Merke:
Eine Zeichenkette kann nur in Kenntnis der verwendeten Codierung sinnvoll bearbeitet und interpretiert werden.
Das bedeutet auch, dass Tastatur, Editor und Anzeige in der Codierung übereinstimmen müssen. Sie haben sicher schon den Effekt bemerkt, dass Umlaute, die ein Programm auf der Konsole ausgibt, nicht korrekt dargestellt werden. Die Ursache ist die fehlende Übereinstimmung der Codierung. In Linux wird die Codierung des Betriebssystems durch die Variable LANG (für language) eingestellt. Oft ist de_DE.UTF-8 üblich. Der de_DE-Anteil sorgt dabei für die hier übliche Dezimalpunkt- und Datumsdarstellung usw. Diese Aspekte werden Facetten genannt; Einzelheiten folgen.
Windows: Durch Anklicken des Fenstersymbols ganz oben links im Eingabeaufforderungsfenster geht man über »Eigenschaften« zu den Schriftarten. Dort Lucida Console wählen. Dann chcp 65001 eintippen.
Linux: Normalerweise ist die Konsole auf UTF-8 eingestellt, siehe Ausgabe des Befehls locale. Falls etwas anderes gewünscht ist: Im Konsolenfenster oben »Einstellungen/Bearbeiten« anklicken.
macOS: Normalerweise ist die Konsole auf UTF-8 eingestellt, siehe Ausgabe des Befehls locale. Falls etwas anderes gewünscht ist: Oben bei geöffneter Konsole den Reiter »Terminal« anklicken, dann »Profile« und bei »Textcodierung« UTF-8 einstellen. Das nächste neu geöffnete Terminal-Fenster hat die neue Codierung.
»Normale« Zeichenliterale werden durch Hochkommas gekennzeichnet, zum Beispiel ’z’. Anstelle des Zeichens z können alle anderen Zeichen des Basiszeichensatzes, der normalerweise in etwa ASCII entspricht, verwendet werden, außer dem Hochkomma selbst wegen der Begrenzerfunktion, dem Backslash und dem Zeilenendezeichen. Mit dem Backslash werden Sonderzeichen eingeleitet, wie die Tabelle 1.5 auf Seite 55 zeigt. Es gibt aber weitere Möglichkeiten, die durch ein Präfix markiert werden. So repräsentieren U und u definierte Zeichensätze.
u8’z’ : u8 vor ’z’ kennzeichnet das Zeichen als ein Zeichen des Typs char8_t, das der UTF-8-Codierung entspricht. Die ersten 128 UTF-8-Zeichen stimmen mit ASCII überein. u8 garantiert eine ASCII-Codierung auch auf Nicht-ASCII-Systemen. Mehrere Bytes, etwa zur Darstellung von Sonderzeichen, sind nicht erlaubt.
u’z’ : Ein vorangestelltes u besagt, dass das Literal den Typ char16_t hat. Es muss mit 16 Bits darstellbar sein und sein Wert wird durch ISO 10646 definiert. ISO 10646 ist eine Norm für den Universal Character Set (UCS), der die Unterformate UCS-2 und UCS-4 hat, entsprechend einer Codierung in 2 bzw. 4 Bytes. Das Standardisierungsgremium für ISO 10646 arbeitet mit dem Unicode-Gremium zusammen, um die verschiedenen Definitionen nicht auseinanderlaufen zu lassen. So entspricht UCS-4 UTF-32.
U’z’ : Ein vorangestelltes U ist dementsprechend ein char32_t-Zeichen.
L’z’ : Ein großes L steht für ein wchar_t-Zeichen. Das »w« steht für »wide«. Dieser Typ ist implementationsabhängig und für Zeichensätze gedacht, die nicht mit einem Byte pro Zeichen auskommen. Die 1-Byte-Zeichen heißen im Gegensatz dazu »narrow characters«.
Den C++-Zeichenliteralen stehen entsprechende Stringliterale gegenüber:
Wenn das Programm im ISO 8859-1-Format abgespeichert wird, werden im folgenden Beispiel die den Umlauten entsprechenden Zahlenwerte ausgegeben:
#include <iostream> #include <locale> #include <string> #include <type_traits> // is_signed using namespace std; int main() { locale loc; cout << "loc.name()=˽" << loc.name() << ’\n’; locale dt("de_DE.utf8"); cout << "dt.name()=˽" << dt.name() << ’\n’; locale vorher = locale::global(dt); // dt für alles setzen string s("ÄÖÜäöüß"); cout.imbue(dt); if (s.length() > 7) { cerr << "FEHLER:˽DIESES˽PROGRAMM˽IST˽NICHT˽in˽ISO˽8859˽gespeichert" "˽und˽wird˽nicht˽wie˽beabsichtigt˽funktionieren!˽Abbruch!\n"; return 1; } cout << "\nProgramm˽funktioniert˽nur˽richtig˽bei˽auf˽ISO˽8859" "˽eingestellter˽Konsole!˽\n"; for (unsigned int i = 0; i < s.length(); ++i) { cout << "Zeichen˽" << i << ":˽" << s[i] << " "; // ggf. Korrektur für signed char int intwert = static_cast<int>(static_cast<unsigned char>(s[i])); cout << intwert << ’\n’; } for (auto c : s) { cout << toupper(c, dt) << tolower(c, dt) << " "; } for (auto& c : s) { c = toupper(c, dt); } cout << "\nNach˽toupper:˽" << s << ’\n’; // Andere Möglichkeit, jetzt mit tolower: use_facet<ctype<char>>(dt).tolower(&s[0], s.c_str() + s.length()); cout << "Nach˽tolower:˽" << s << ’\n’; } // ISO 8859-1: Bits 0-127 wie ASCII reicht für sehr viele Sprachen aus, // hat aber kein Euro-Symbol // Ä 196 0xC4 ä 228 0xE4 // Ö 214 0xD6 ö 246 0xF6 // Ü 220 0xDC ü 252 0xFC // ß 223 0xDF // Euro ist 164 0xA4 bei ISO 8859-15
Die Konsole muss natürlich auch auf ISO 8859-1 eingestellt sein. Die Anweisung s[i] = toupper(s[i], dt); hätte auch durch s[i] = toupper(s[i]);, also Aufruf der entsprechenden C-Funktion ersetzt werden können, weil die locale-Einstellung vorher auf de_DE gesetzt worden ist. Die aktuelle Einstellung wird von toupper(char) berücksichtigt. Die ISO 8859-1-codierte Zeile
könnte durch
ersetzt werden. Das wäre zwar von jedem Editor lesbar, egal mit welcher Codierungseinstellung. Aber portabel wäre auch das nicht, denn UTF-8-codiert sehen die Umlaute so aus:
Merke:
Ein Programm, das Zeichenketten mit Nicht-ASCII-Zeichen enthält, ist nicht portabel.
Unter portabel wird hier verstanden, dass der Quellcode auf ein beliebiges anderes System übertragen und dort übersetzt werden kann und dass das Programm unabhängig von der locale-Einstellung und der Umgebung dasselbe Ergebnis liefert. Im Folgenden sehen Sie ein Beispiel, das UTF-8-codiert ist und einen wchar_t-String benutzt.
1 #include <fstream> 2 #include <iostream> 3 #include <locale> 4 #include <string> 5 using namespace std; 6 7 int main() 8 { 9 locale dt("de_DE.utf8"); 10 locale::global(dt); // dt fuer alles setzen 11 wstring ws(L"ÄÖÜäöüß"); 12 wcout.imbue(dt); 13 wcout << "Ausgabe˽mit˽wcout:˽" << ws << ’\n’; 14 wcout << L"Länge=" << ws.length() << ’\n’; 15 wcout << "sizeof(wchar_t):˽" << sizeof(wchar_t) << ’\n’; 16 wofstream wdatei("ausgabe.txt"); 17 wdatei << "Ausgabe˽in˽wofstream:˽" << ws << ’\n’; 18 for (unsigned int i = 0; i < ws.length(); ++i) { 19 wdatei << "WZeichen˽" << i << ":˽" << ws[i] << ’\n’; 20 } 21 for (auto& wc : ws) { 22 wc = toupper(wc, dt); 23 } 24 wcout << "nach˽toupper():˽" << ws << ’\n’; 25 }
Die Konsole muss natürlich auch auf UTF-8 eingestellt sein, um eine lesbare Anzeige zu bekommen. Bemerkungen zu diesem Beispiel:
Bei einem auf UTF-8 eingestellten Editor wird der Quellcode in UTF-8 gespeichert. Nicht-ASCII-Zeichen werden also Multibyte-Sequenzen (Zeile 11).
Mit wcout, der cout-Entsprechung für wchar_t-Zeichen, wird der wstring ws auf der Konsole ausgegeben (Zeile 13).
Die interne Darstellung eines wstrings im ausführbaren Programm ist ein Array mit n wchar_t-Zeichen, wobei n die Anzahl der Zeichen (nicht der Bytes!) ist, wie sie in Zeile 14 angezeigt wird.
wchar_t ist ein int-Typ. Seine Größe wird in Zeile 15 ausgegeben (auf meinem System 4 Byte).
Die folgende Ausgabe wird in eine Datei des Typs wofstream umgeleitet (Zeilen 16 bis 20).
Die Funktion toupper() (Zeile 22) funktioniert zeichenweise unter Berücksichtigung der übergebenen locale-Einstellung.
Wie man sieht, ist die Arbeit mit Wide-Strings nicht so komfortabel wie mit »normalen« Strings. Auch wird die Umwandlung von Strings verschiedener Codierungen, wie es das Programm iconv (siehe Tipp) leistet, noch kaum unterstützt.
Tipp
Der Befehl iconv (Windows: MinGW-Version von iconv) wandelt Dateiformate um. Aufruf zum Beispiel: iconv -f ISO-8859-1 -t UTF-8 -o utf8.txt deDE.txt
Dabei bedeuten -f (from) das Quellformat, -t (to) das Zielformat. Nach -o folgt der Name der Ausgabedatei; zuletzt der Name der zu konvertierenden Datei. iconv -l zeigt alle dem Programm bekannten Zeichensatzcodierungen an. Dabei sind einige unter mehreren Namen aufgeführt. Zum Beispiel sind die ISO-10464 UCS-Codes dasselbe wie UTF. Das Wort »bekannt« bedeutet in diesem Zusammenhang nicht, dass von jeder Codierung zu jeder anderen konvertiert werden kann.
30.3 | Zeichenklassifizierung und -umwandlung |
Die Definition, ob zum Beispiel ein spezielles Zeichen ein Buchstabe oder etwas anderes ist, hängt von der Sprache ab. Aus diesem Grund gibt es die Funktionen der Tabellen 33.1 und 33.2 (Seite 952 f.) in einer sprachumgebungsabhängigen Variante für verschiedene Zeichentypen charT:
template<typename charT> bool isspace (charT c, const locale& loc); template<typename charT> bool isprint (charT c, const locale& loc); template<typename charT> bool iscntrl (charT c, const locale& loc); template<typename charT> bool isupper (charT c, const locale& loc); template<typename charT> bool islower (charT c, const locale& loc); template<typename charT> bool isalpha (charT c, const locale& loc); template<typename charT> bool isdigit (charT c, const locale& loc); template<typename charT> bool ispunct (charT c, const locale& loc); template<typename charT> bool isxdigit(charT c, const locale& loc); template<typename charT> bool isalnum (charT c, const locale& loc); template<typename charT> bool isgraph (charT c, const locale& loc);
template<typename charT> charT toupper(charT c, const locale& loc); template<typename charT> charT tolower(charT c, const locale& loc);
30.4 | Kategorien |
Locale-Sprachumgebungen enthalten verschiedene Kategorien, die in Facetten unterteilt sind. Das abfragbare Datum locale::category ist eine int-Bitmaske, die die Oder-Verknüpfung aller oder eines Teils der folgenden Konstanten ist: none, ctype, monetary, numeric, time und messages. Jede dieser Kategorien definiert eine Menge lokaler Facetten, wie die Tabelle 30.1 zeigt. Die Facetten sind Template-Klassen, die als Argument den Typ char oder wchar_t für wide characters haben können.
Kategorie |
Facetten |
Zweck |
collate |
collate<charT> |
Zeichenvergleich |
ctype |
ctype<charT> |
Zeichenklassifizierung |
numeric |
numpunct<charT> |
Zahlenformatierung |
num_get<charT> |
Eingaben |
|
num_put<charT> |
Ausgaben |
|
monetary |
moneypunct<char, |
Währungsformatierung (Intl = true für internationale Festlegungen) |
money_get<charT> |
Eingaben |
|
money_put<charT> |
Ausgaben |
|
messages |
messages<charT> |
Strings aus Message-Katalogen holen |
30.4.1 | collate |
Die Klasse template<typename charT> class collate ist eine Facette, die die Funktionen für Vergleiche von Zeichenketten kapselt. Sie besitzt die folgenden öffentlichen Elementfunktionen:
int compare(const charT* low1, const charT* high1,
const charT* low2,const charT* high2) const
Diese Funktion vergleicht zwei Zeichenketten, die durch die Intervalle [low1, high1) und [low2, high2) definiert werden (zur Definition von Intervallen siehe Seite 980). Es wird 1 zurückgegeben, falls die erste Zeichenkette größer als die zweite ist, –1 im umgekehrten Fall und 0 bei Gleichheit. Der Operator locale::operator()() von Seite 914 ruft diese Funktion auf. Das Beispiel auf Seite 719 zeigt, wie ein locale-Objekt zur sprachlich korrekten Sortierung eingesetzt wird.
basic_string<charT> transform(const charT* low, const charT* high) const
gibt das String-Äquivalent des Bereichs zurück, wobei die Ordnungsrelationen erhalten bleiben, d.h. ein Vergleich zweier erzeugter Strings mit dem Algorithmus lexicographical_compare (siehe Seite 755) muss zum selben Ergebnis wie compare() führen.
long hash(const charT* low, const charT* high) const
gibt einen Hash-Wert für den übergebenen Bereich zurück. Dabei ist gewährleistet, dass der Hash-Wert auch bei unterschiedlichen Werten im Bereich stets derselbe ist, wenn nur compare() die Bereiche als gleich ansieht, d.h. 0 zurückgibt. Ein Beispiel dafür könnte sein, dass ä und ae bei einer Sortierung gleich behandelt werden sollen.
30.4.2 | ctype |
Die Klasse template<typename charT> class ctype kapselt die Funktionen für Zeichenklassifizierung und -umwandlung. Es existiert eine Spezialisierung ctype<char>. So gibt zum Beispiel der Aufruf der Funktion toupper(c, loc) von Seite 920 für Zeichen des Typs char nichts anderes als use_facet<ctype<char> >(loc).toupper(c) zurück. ctype erbt von der Basisklasse ctype_base, die eine Bitmaske mask für Klassifizierungszwecke nach [ISOC++, category.ctype] etwa wie folgt definieren könnte:
class ctype_base { public: using mask = T; static const mask space = 1 << 0; static const mask print = 1 << 1; static const mask cntrl = 1 << 2; static const mask upper = 1 << 3; static const mask lower = 1 << 4; static const mask alpha = 1 << 5; static const mask digit = 1 << 6; static const mask punct = 1 << 7; static const mask xdigit = 1 << 8; static const mask blank = 1 << 9; static const mask alnum = alpha | digit; static const mask graph = alnum | punct; };
Der Typ T und damit mask muss ein Typ sein, der Bitoperationen erlaubt, also etwa int oder ein enum. Die öffentliche Schnittstelle enthält die folgenden Methoden:
bool is(mask m, charT c) const gibt zurück, ob c zur Klassifizierung m passt.
const charT* is(const charT* low, const charT* high, mask* carr) const
Diese Funktion berechnet einen Wert vom Typ ctype_base::mask für jedes der Zeichen im Intervall [low, high) und legt das Ergebnis im C-Array carr beginnend an der Stelle vec[0] ab. high wird zurückgegeben.
const charT* scan_is(mask m, const charT* low, const charT* high) const
gibt einen Zeiger auf das erste Zeichen im Intervall [low, high) zurück, das der Klassifizierung m genügt. Existiert kein solches Zeichen, wird high zurückgegeben.
const charT* scan_not(mask m, const charT* low, const charT* high) const
gibt einen Zeiger auf das erste Zeichen im Intervall [low, high) zurück, das nicht der Klassifizierung m genügt. Existiert kein solches Zeichen, wird high zurückgegeben.
charT toupper(charT c) const
gibt den entsprechenden Großbuchstaben zurück, sofern ein solcher existiert. Andernfalls wird das Argument zurückgegeben.
const charT* toupper(charT* low, const charT* high) const
verwandelt alle Zeichen im Bereich [low, high) in Großbuchstaben, sofern solche existieren. Es wird high zurückgegeben.
charT tolower(charT c) const
gibt den entsprechenden Kleinbuchstaben zurück, sofern ein solcher existiert. Andernfalls wird das Argument zurückgegeben.
const charT* tolower(charT* low, const charT* high) const
verwandelt alle Zeichen im Bereich [low, high) in Kleinbuchstaben, sofern solche existieren. Es wird high zurückgegeben. Im Listing 30.3 von Seite 917 könnte der String s unter Verwendung des locale-Objekts dt wie folgt umgewandelt werden:
Der Vorspann use_facet<ctype<char> >(dt) gibt die Facette ctype des locale-Objekts dt zurück, deren Funktion tolower() dann aufgerufen wird.
charT widen(char c) const
wandelt c in eine entsprechende Repräsentation des Typs charT um (z.B. wide character wchar_t).
const char* widen(const char* low, const char* high, charT* to) const
wandelt jedes Zeichen im Intervall [low, high) in eine entsprechende Repräsentation des Typs charT um und legt das Ergebnis in to ab. Der Rückgabewert ist high.
char narrow(charT c, char dfault) const
wandelt c in eine entsprechende Repräsentation des Typs char um, falls eine solche existiert. Andernfalls wird dfault zurückgegeben.
const charT* narrow(const charT* low, const charT* high, char vorgabe,
char* to) const
wandelt jedes Zeichen im Intervall [low, high) in eine entsprechende Repräsentation des Typs char um, falls eine solche existiert. Andernfalls wird vorgabe genommen. Das Ergebnis wird in to abgelegt. Der Rückgabewert ist high.
30.4.3 | numeric |
Die Template-Klassen num_get und num_put wickeln das formatierte Einlesen bzw. die formatierte Ausgabe ab. Sie werden intern von den Standard-Iostreams benutzt, um Zahlen mit national bedingten Dezimal- und Tausendermarkierungen richtig zu bearbeiten, und sind für normale Fälle wohl kaum von Bedeutung, da sie versteckt innerhalb des <<- bzw. >>-Operators Anwendung finden, wie das folgende Beispiel zeigt:
#include <iostream> #include <locale> using namespace std; int main() { cin.imbue(locale("de_DE.utf8")); cout.imbue(locale("en_US.utf8")); double f = 0.0f; while (cin >> f) { // implizite Nutzung von num_get cout << f << ’\n’; // implizite Nutzung von num_put } }
Mit den gegebenen locale-Objekten würde die Eingabe 3.456,78 die Ausgabe 3,456.78 bewirken. Die Abfrage der Markierungen und anderer Dinge mit der Klasse numpunct folgt.
Die Facette template<class charT> class numpunct hat die folgende öffentliche Schnittstelle:
charT decimal_point() const
gibt den verwendeten Dezimalpunkt zurück (z. B. einen Punkt für en_US oder ein Komma für de_DE).
charT thousands_sep() const
gibt das Trennzeichen zwischen Tausender-Gruppen zurück.
string grouping() const
Die Zeichen des zurückgegebenen Strings, im Folgenden str genannt, sind als ganzzahlige Zahlen zu interpretieren, die die Anzahl der Ziffern in der Gruppe angeben, beginnend mit Position 0 als am weitesten rechts stehende Gruppe. Wenn str.size() <= i für eine Position i gilt, ist die Zahl dieselbe wie die für Position i-1. Zum Beispiel wird die Anzahl der Ziffern einer Tausendergruppe gleich 3 sein, d.h. S == "\03". Negative Zahlen charakterisieren unbegrenzte Gruppen wie etwa Zahlen ganz ohne Markierung der Tausender (Beispiel siehe cppbuch/k30/numpunct.cpp).
basic_string<charT> truename() const und
basic_string<charT> falsename() const
geben den verwendeten Namen (true bzw. false) für die Ausgabe zurück, sofern boolalpha == true ist (vgl. Abschnitt 9.4, Seite 434).
Die Klasse kann für ein bestimmtes locale-Objekt wie folgt benutzt werden:
locale loc; // Kopie des aktuellen globalen locale-Objekts char dezPunkt = use_facet<numpunct<char> >(loc).decimal_point(); // oder string wahr = use_facet<numpunct<char> >(loc).truename(); cout << wahr; // Ausgabe: true
30.4.4 | monetary |
Diese Kategorie enthält alles, was für die formatierte Ein- und Ausgabe von Geldbeträgen einschließlich der Währungsangaben gebraucht wird. In den folgenden Beispielen wird von einer einfachen Klasse Geld ausgegangen.
#ifndef GELD_H #define GELD_H #include <iostream> class Geld { public: Geld(long int b = 0L); [[nodiscard]] long int getBetrag() const; private: long int betrag; }; std::istream& operator>>(std::istream& is, Geld& G); std::ostream& operator<<(std::ostream& os, const Geld& G); #endif
Die Facette template<class charT> class moneypunct definiert Währungssymbole und die Formatierung. Sie erbt von der Klasse money_base, die die öffentlichen Elemente
bereitstellt. Ein monetäres Format wird durch eine Folge von vier Komponenten spezifiziert, die in einem pattern-Objekt p zusammengefasst werden. Das Element static_cast< part>(p.field[i]) bestimmt die i-te Komponente des Formats. Aus Effizienzgründen ist field vom Typ char anstatt vom Typ part. Im Feld eines pattern-Objekts kann eines der Elemente von part genau einmal vorkommen. Die Klasse money_punct hat die folgende öffentliche Schnittstelle:
charT decimal_point() const gibt den verwendeten Dezimalpunkt zurück.
charT thousands_sep() const
gibt das Trennzeichen zwischen Tausender-Gruppen zurück.
string grouping() const Bedeutung wie bei numpunct (Seite 923).
basic_string<charT> curr_symbol() const
gibt das Währungssymbol zurück, z.B. $. Für internationale Instanziierungen (vgl. Tabelle 30.1, Seite 920) werden im Allgemeinen drei Buchstaben und ein Leerzeichen zurückgegeben, z.B. »USD «.
basic_string<charT> positive_sign() const und
basic_string<charT> negative_sign() const
geben das Zeichen für einen positiven Wert (+ oder Leerzeichen) bzw. einen negativen Wert (-) als String zurück.
int frac_digits() const
gibt die Ziffern nach dem Dezimalpunkt an, im Allgemeinen zwei.
pattern pos_format() const und
pattern neg_format() const
geben das benutzte Formatierungsmuster zurück. Das Standardmuster ist {symbol, sign, none, value}.
Die Klasse kann für ein bestimmtes locale-Objekt wie folgt benutzt werden:
Die Klasse template<class charT> class money_get wickelt das formatierte Einlesen von Geldbeträgen, ggf. mit Währungsangaben, ab. Sie hat zwei öffentliche Methoden
iter_type get(iter_type s, iter_type end, bool intl, ios_base& f,
ios_base::iostate& err, long double& units) const
iter_type get(iter_type s, iter_type end, bool intl, ios_base& f
ios_base& f, ios_base::iostate& err, string_type& units) const
iter_type ist eine öffentliche, in der Klasse definierte Typbezeichnung für einen Input-Iterator, dessen Typ mit istreambuf_iterator<charT> vorgegeben ist. Dieser Typ wird nicht weiter beschrieben, weil erstens der Typ über den Namen money_get::iter_type benutzbar ist und er zweitens im Allgemeinen nicht alleinstehend benötigt wird, wie das Beispiel unten zeigt. string_type ist der in der Klasse definierte Name für den Typ basic_string<charT>. Diese Methoden lesen einen Geldbetrag als double-Zahl bzw. einen String ein, wobei der Dezimalpunkt eliminiert wird. Sie können in einer benutzerdefinierten Klasse zur Implementierung des Eingabeoperators (>>) verwendet werden. Zurückgegeben wird ein Iterator, der auf das unmittelbar nach dem letzten gültigen Zeichen eines Geldbetrags folgende Zeichen verweist. Im folgenden Beispiel wird Bezug auf die oben erwähnte Klasse Geld genommen. Die Funktion getloc() gibt die mit dem Stream assoziierte locale zurück.
#include "Geld.h" #include <locale> Geld::Geld(long int b) :betrag(b) {} long int Geld::getBetrag() const { return betrag; } std::istream& operator>>(std::istream& is, Geld& geld) { std::istream::sentry s(is); // Ein sentry-Objekt bereitet den Stream vor. if (s) { auto fehler = is.rdstate(); is.setf(std::ios::showbase); // Damit die Währung ausgewertet wird. long double wieviel = 0; std::use_facet<std::money_get<char> >(is.getloc()) .get(is, 0, false, is, fehler, wieviel); is.setstate(fehler); if (!fehler) { geld = Geld(static_cast<long int>(wieviel)); } else { std::cerr << "fehlerhafte˽Eingabe!\n"; } } return is; }
Zwar ist die Basis der Klasse Geld ein ganzzahliger Cent-Betrag, die obige put()-Funktion verlangt jedoch long double. Aus diesem Grund wird die Typumwandlung static_cast eingesetzt. Das Beispiel zeigt, dass der Istream is an die Stelle des verlangten Input-Iterators treten kann. Der Grund liegt darin, dass die Klasse istreambuf_iterator<charT> einen Konstruktor hat, der ein istream-Objekt als Parameter nimmt. Der vierte Parameter von get() nutzt aus, dass die Klasse istream von der Klasse ios_base erbt. Er dient dazu, intern über getloc() auf die Facette moneypunct zuzugreifen. Das folgende Programmfragment zeigt eine Anwendung:
Eine Zeichenfolge "1056.23" im Eingabestrom führt zu dem Ergebnis
Die Template-Klasse template<class charT> class money_put wickelt die formatierte Ausgabe von Geldbeträgen, gegebenenfalls mit Währungsangaben, ab. Sie hat zwei öffentliche Methoden:
iter_type put(iter_type s, bool intl, ios_base& f,
charT fill, long double& units) const
iter_type put(iter_type s, bool intl, ios_base& f,
charT fill, string_type& digits) const
Die Typbezeichnungen entsprechen denen der Klasse money_get, wobei der Typ iter_type natürlich ein Output-Iterator ist. Anwendungsmöglichkeiten ergeben sich in Analogie zur Klasse money_get, zum Beispiel der Ausgabeoperator für die obige Klasse Geld:
std::ostream& operator<<(std::ostream& os, const Geld& geld) { std::ostream::sentry s(os); // Ein sentry-Objekt bereitet den Stream vor. os.setf(std::ios::showbase); // Damit die Währung angezeigt wird. if (s) { std::use_facet<std::money_put<char> >(os.getloc()) .put(os, true, os, ’ ’, static_cast<double>(geld.getBetrag())); } return os; }
30.4.5 | messages |
Die Klasse template<class charT> class messages implementiert das Holen von Meldungen aus Katalogen, ähnlich wie das GNU-Programm gettext()2. Wie ein Katalog realisiert ist, ob zum Beispiel als Datei oder Teil einer Datenbank, ist implementationsabhängig. Der Typ catalog, ein int-Typ, steht für eine Katalognummer. Es gibt die folgenden Elementfunktionen:
catalog open(const string& fn, const locale&) const
eröffnet den Katalog, der durch den String fn identifiziert wird, und gibt eine Identifizierungszahl zurück, die bis zum folgenden close() zu dem Katalog gehört. Falls diese Zahl negativ ist, kann der Katalog nicht geöffnet werden.
void close(catalog c) schließt den Katalog c.
basic_string<charT> get(catalog c, int set, int msgid,
const basic_string<charT>& vorgabe) const
Es wird die durch die Argumente set, msgid und vorgabe identifizierte Meldung zurückgegeben. Falls keine Meldung gefunden wird, ist vorgabe das Ergebnis.
Durch Setzen der Sprachumgebung werden aus dem Katalog die übersetzten Meldungen geholt. Das Programm gibt auf meinem Linux-System aus:
#include <iostream> #include <locale> #include <string_view> int main() { // Funktioniert nicht unter Windows. // MinGW für Windows hat keine locale außer C und "" implementiert. std::locale loc("de_DE.utf8"); std::cout.imbue(loc); auto& facet = std::use_facet<std::messages<char>>(loc); std::string_view software{"grep"}; // Katalog für das Programm grep auto cat = facet.open(software.data(), loc); if (cat < 0) { std::cout << "Deutscher˽" << software << "-Message-Katalog˽kann˽nicht˽geöffnet˽werden\n"; } else { std::cout << "invalid˽character˽class˽->˽" << facet.get(cat, 0, 0, "invalid˽character˽class") << ’\n’ << "no˽syntax˽specified˽˽˽˽˽->˽" << facet.get(cat, 0, 0, "no˽syntax˽specified") << ’\n’ << "unbalanced˽˽˽˽˽˽˽˽˽˽˽˽(˽->˽" << facet.get(cat, 0, 0, "unbalanced˽(") << ’\n’ << "write˽˽˽˽˽˽˽˽˽˽˽˽˽error˽->˽" << facet.get(cat, 0, 0, "write˽error") << ’\n’; } facet.close(cat); }
30.5 | Konstruktion eigener Facetten |
Man kann vorhandene Facetten durch eigene ersetzen. Dazu muss man wissen, dass zu allen Methoden der oben beschriebenen Facetten zusätzliche virtuelle Methoden mit exakt denselben Schnittstellen existieren, die ein vorangestelltes do_ im Namen haben und protected sind. Diese Methoden werden von den oben beschriebenen aufgerufen. Ferner gibt es Klassen, die von den beschriebenen Facetten nur die protected-Schnittstelle erben und im Namen ein nachgestelltes _byname tragen, um auszudrücken, dass Namen für die Facetten vergeben werden können. Von diesen Klassen können eigene Klassen abgeleitet und die Methoden überschrieben werden. Das folgende Beispiel zeigt, wie das vorgegebene Standardsymbol für Euro, nämlich EUR, mithilfe einer eigenen Facette für Währungssymbole durch das Symbol e ersetzt wird.
#include "Geld.h" #include <iostream> #include <locale> #include <string> using MeinMoneypunct = std::moneypunct_byname<char, true>; // true für Internationalisierung class MeinWaehrungsformat : public MeinMoneypunct { protected: // Überschreiben der virtuellen Funktion do_curr_symbol(), die von der //public-Funktion curr_symbol() der Basisklasse moneypunct gerufen wird: std::string do_curr_symbol() const override { return wsymbol; } public: MeinWaehrungsformat(const std::locale& loc, const char* ws) : MeinMoneypunct(loc.name().c_str()), wsymbol(ws) {} private: const char* wsymbol; }; using namespace std; int main() { locale locUS("en_US.utf8"); Geld derBetrag; cout << "Eingabe˽in˽Cent(!),˽z.B.˽123456:?"; cin >> derBetrag; cout << "direkte˽Abfrage˽mit˽voreingestellter˽locale˽(" << locale().name() // locale ’C’ << ")˽:" << derBetrag.getBetrag() << ’\n’; // 123456 cout.imbue(locUS); // cout auf enUS umschalten cout << "Es˽wurde˽" << derBetrag << "˽eingegeben˽(US-Format).\n";// USD 1,234.56 locale deDEeuro("de_DE.utf8"); cout << "Ausgabe˽Standard-Währungssymbol˽EUR˽und˽Dezimalkomma˽statt˽" "Dezimalpunkt˽:\n"; // Achtung: KEINE Währungsumrechnung, nur Darstellungsänderung! cout.imbue(deDEeuro); // cout auf deDE@euro umschalten cout << derBetrag << ’\n’; // 1.234,56 EUR MeinWaehrungsformat mwf(deDEeuro, "\u20ac"); // Euro in UTF-8 // \xA4 in ISO-8859-15 cout << "Ausgabe˽eigenes˽Währungssymbol˽und˽Dezimalkomma˽statt˽" "Dezimalpunkt˽:\n"; cout.imbue(locale(deDEeuro, &mwf)); cout << derBetrag << ’\n’; // 1.234,56 e }
1 Es werden wie üblich 8 Bits pro Byte angenommen. Ausnahmen gibt es, aber nur wenige.