String-Objekte I
string-Datentyp
Für die Bearbeitung von Strings stellt die C++-Standardbibliothek den Datentyp string zur Verfügung. Er ist in der Header-Datei string definiert liegt ebenfalls im Namensraum std.
Gegenüber der Ablage von C-Strings in Feldern bietet er u.a. folgende Vorteile:
- Es wird stets genügend Speicher reserviert, um eine beliebig lange Zeichenfolge ablegen zu können.
- Es stehen diverse Operatoren zur Verfügung, um Strings auf einfache Art und Weise bearbeiten zu können. So fügt zum Beispiel der Operator + zwei Strings zusammen.
Der Datentyp string enthält wesentlich mehr Funktionalität, als in dieser ersten Einführung aufgezeigt wird. Da aber noch einige Grundlagen fehlen, wird auf diesen Datentyp in einem späteren Kapitel String-Objekte II nochmals eingegangen.
Damit in Zukunft keine Verwirrung bezüglich des Begriffs String entsteht, wird im weiteren Verlauf unter einem String immer ein string-Objekt verstanden. Zeichenfolgen, die in einem char-Feld abgelegt sind, werden als C-String bezeichnet.
String definieren
Prinzipiell wird ein String auf die gleiche Art und Weise definiert wie eine Variable.
std::string sName;
sName ist der Name des string-Objekts. Ein auf diese Weise definierter String hat noch keinen Inhalt.
Um einen String bei seiner Definition mit einer Zeichenfolge zu initialisieren, folgt nach dem Namen des string-Objekts
- entweder der Zuweisungsoperator und eine in Anführungszeichen eingeschlossene Zeichenfolge oder
- eine runde oder geschweifte Klammer und darin, ebenfalls in Anführungszeichen eingeschlossen, die Zeichenfolge.
Beide Verfahren sind gleichwertig und führen zum Aufruf des sogenannten Konstruktors des string-Objekts. Was ein Konstruktor ist und welche Aufgabe er hat, wird im Kapitel Konstruktor und Destruktor behandelt. Für den Augenblick soll die Aussage genügen, dass der Konstruktor das string-Objekt mit der übergebenen Zeichenfolge initialisiert.
Wie im Kapitel Konstanten erwähnt, ist der Datentyp eines C-String-Literals "char-Feld mit n konstanten Zeichen". Um explizit ein String-Literal zu definieren, folgt nach dem abschließenden Anführungszeichen das Suffix s. Und damit der Compiler das Suffix s kennt, ist dieses mittels
using namespace std::string_literals;
einzublenden.
Wird eine auto-Variable mit einer Zeichenfolge ohne das Suffix s initialisiert, ist der Datentyp der auto-Variable standardmäßig ein const char*. Soll die auto-Variable vom Typ string sein, muss die Zeichenfolge mit dem Suffix s gekennzeichnet werden.
Und selbstverständlich kann auch vom Datentyp string ein Feld definiert werden, das n Strings aufnimmt, die unterschiedliche Längen besitzen können. Dies darf nicht mit der Definition eines char-Feldes verwechselt werden.
// Header-Datei für string einbinden
#include <string>
// Suffix s fuer string einblenden
using namespace std::string_literals;
// Leeren String definieren
std::string empty;
// String mit C-String initialisieren
std::string text1 = "Ein C-String";
// String-Feld definieren
std::string array[10];
// und ersten Element einen C-String zuweisen
array[0] = "String-Element 0";
// String mit string-Literal initialsieren
std::string text3 {"Ein String"s};
// String mit einem anderen String initialisieren
std::string text2(text1);
Die Standardbibliothek kennt weitere Methoden, um ein string-Objekt zu erstellen. Für unsere Zwecke sollen diese Möglichkeiten vorerst ausreichen.
Ein- und Ausgabe eines Strings
Die Ausgabe eines Strings unterscheidet sich nicht von der Ausgabe anderer Daten, d.h., der String wird direkt in den Ausgabestream einfügt.
#include <string>
#include <iostream>
#include <print>
// Suffix s fuer string einblenden
using namespace std::string_literals;
int main()
{
auto text = "Ein string-Objekt"s;
std::cout << text << '\n';
std::println("Text: {}", text);
}
Ein string-Objekt
Text: Ein string-Objekt
Beim Einlesen ist zu unterscheiden, ob jedes Wort der Eingabe in einem eigenen String abzulegen ist oder die gesamte Eingabe in einem String.
Um mehrere Wörter einer Eingabe in getrennten Strings abzulegen, sind entsprechend viele string-Objekte beim Einlesen anzugeben.
#include <string>
#include <iostream>
#include <print>
int main()
{
std::string word1, word2;
std::print("2 Worte eingeben: ");
std::cin >> word1 >> word2;
std::println("Eingabe war: {},{}",word1,word2);
}
2 Worte eingeben: ein Text
Eingabe war: ein,Text
Soll dagegen eine ganze Zeile in einem String abgelegt werden, ist hierfür die Funktion std::getline() einzusetzen. getline() erhält im ersten Parameter eine Referenz auf den Stream, aus dem die Zeile eingelesen wird. Im Beispiel ist dies der Eingabestream cin. Ebenso wäre hier z.B. die Angabe eines Dateistreamobjekts vom Typ ifstream möglich. Der zweite Parameter ist dann die Referenz auf das string-Objekt, in dem die Eingabe abgelegt wird.
#include <string>
#include <iostream>
#include <print>
int main()
{
std::string line;
std::print("Bitte einen Text eingeben: ");
std::getline(std::cin,line);
std::println("Eingabe: {}",line);
}
Bitte einen Text eingeben: einlesen mit std::getline()
Eingabe: einlesen mit std::getline()
String-Operationen
Zuweisungen
Die einfachste Operation ist die Zuweisung. Mit ihr wird einem String ein (C-)String-Literal, eine (C-)String-Konstante oder ein weiterer String zugewiesen.
// Zwei Strings definieren
std::string s1, s2;
// C-String-Literal einem String zuweisen
s1 = "Dies ist String1";
// String einem anderen String zuweisen
s2 = s1;
Addition
Strings werden mit dem Operator + bzw. =+ 'addiert', d.h. aneinandergehängt.
#include <string>
#include <print>
using namespace std::string_literals;
int main()
{
// Drei Strings definieren
std::string s1("Text1"), s2, s3;
std::println("String 1: {}",s1);
// C-String-Literal addieren, s1: "Text1 erweitert"
s1 += " erweitert";
std::println("String 1: {}",s1);
// char* addieren, s2: "Text1 erweitert in s2"
s2 = s1 + " in s2";
std::println("String 2: {}",s2);
// string addieren, s3: "Text1 erweitert in s3"
s3 = s1 + " in s3"s;
std::println("String 3: {}",s3);
}
String 1: Text1
String 1: Text1 erweitert
String 2: Text1 erweitert in s2
String 3: Text1 erweitert in s3
Beachten Sie den Unterschied zwischen den Zeilen 15 und 18. In Zeile 15 wird ein const char* addiert und in Zeile 18 ein string.
Zugriff auf Zeichen und Stringlänge
Um auf einzelne Zeichen innerhalb eines Strings zuzugreifen, wird der Indexoperator [] verwendet. Auch hier hat das erste Zeichen im String den Index 0. Soll der gesamte String durchlaufen werden, liefert die Methode size() die Anzahl der Zeichen im String als size_t-Datum. Einfacher geht es mit einer range-for-Schleife, um einen String sequenziell zu durchlaufen.
#include <string>
#include <print>
int main()
{
// String definieren und initialisieren
std::string s1("Ein Text\n");
// Alle Zeichem im String einzeln ausgeben
for (size_t i=0Z; i<s1.size(); i++)
std::print("{} ",s1[i]);
// Zugriff mittels range-for
for (auto zeichen: s1)
std::print("{} ",zeichen);
}
E i n T e x t
E i n T e x t
Strings vergleichen
Strings können mit den bekannten Vergleichsoperatoren <, >, == usw. verglichen werden. Der Vergleich erfolgt lexikografisch, d.h., 'Aaaa' ist kleiner als 'Bb'. Einer der beiden Operanden des Vergleichsoperators kann anstelle eines string-Objekts ein const char-Zeiger sein.
#include <string>
#include <print>
int main()
{
// Zwei String definieren und initialisieren
std::string s1("Aaaa"), s2("Bb");
// Strings vergleichen
if (s1 < s2)
std::println("{} < {}",s1,s2);
else
std::println("{} < {}",s2,s1);
}
Aaaa < Bb
char-Zeiger Konvertierung
Die Methode c_str() liefert einen const char-Zeiger auf die im String abgelegte Zeichenfolge. Mithilfe dieser Konvertierung kann überall dort ein String verwendet werden, wo ansonsten ein const char-Zeiger benötigt wird. Es ist zu beachten, dass die Methode c_str() einen const char* liefert und der Inhalt des Strings darüber nicht verändert werden kann. Im Beispiel wird damit der Inhalt eines Strings in ein char-Feld kopiert.
#include <string>
#include <cstring>
#include <print>
int main()
{
// String definieren und initialisieren
std::string s1("Ein Text");
// char-Feld definieren
char charArray[40];
// Zeichen aus String in char-Feld umkopieren
strcpy_s(charArray,sizeof charArray, s1.c_str());
std::println("kopiert: {}",charArray);
}
kopiert: Ein Text
Der umgekehrte Weg, also einen char-Zeiger in einen String zu konvertieren, erfolgt entweder über die Definition eines Strings oder über den Zuweisungsoperator.
Der string-Datentyp enthält, wie erwähnt, weitere Methoden, um z.B. Zeichen in einen String einzufügen oder zu löschen. Diese Methoden werden später im Kapitel String-Objekte II behandelt.
string_view
Ein string_view definiert eine Sicht auf einen bestehenden (C-)String. Wird string_view verwendet, ist die Header-Datei string_view einzubinden. Und wie alle Klassen der Standardbibliothek liegt auch er im Namensraum std.
Ein string_view kann bei seiner Definition u.a. mit einem C-String oder einem String initialisiert werden.
Welchen Vorteil bietet ein string_view, wenn er nur eine Sicht auf einen bestehenden (C-)String ist? Die Antwort darauf lautet: Performance. Sehen Sie sich dazu folgendes Beispiel an:
#include <print>
#include <string>
#include <string_view>
void SRPrint(const std::string& s)
{
std::println("{}",s);
}
void SVPrint(std::string_view s)
{
std::println("{}",s);
}
int main()
{
SRPrint("Ein Text1");
SVPrint("Ein Text2");
}
Ein Text1
Ein Text2
Die Funktion SRPrint() erhält als Parameter eine Referenz auf ein konstantes string-Objekt und die Funktion SVPrint() einen string_view. Da die Funktion SRPrint() ein string-Objekt erwartet, wird vor dem Aufruf der Funktion ein string-Objekt instanziiert und der C-String in das string-Objekt übernommen. Anschließend wird die Funktion ausgeführt. Nach der Rückkehr aus der Funktion wird das string-Objekt nicht weiter benötigt und gelöscht. Wird statt eines string-Objekts ein string_view übergeben, erhält die Funktion einen Verweis auf den übergebenen String, womit der Kopiervorgang entfällt. Die einzige Einschränkung dabei ist, dass der String in der Funktion nicht verändert werden kann.
Als Maschinencode sieht das ganze dann so aus:
;17 : SRPrint("Ein Text");
0x7ff6ba8e17ce lea -0x21(%rbp),%rcx
0x7ff6ba8e17d2 lea 0x1bb16(%rip),%rdx
0x7ff6ba8e17d9 lea -0x50(%rbp),%rax
0x7ff6ba8e17dd mov %rcx,%r8
0x7ff6ba8e17e0 mov %rax,%rcx
0x7ff6ba8e17e3 call 0x7ff6ba8ef110 ... std::allocator<char> const&)>
0x7ff6ba8e17e8 lea -0x50(%rbp),%rax
0x7ff6ba8e17ec mov %rax,%rcx
0x7ff6ba8e17ef call 0x7ff6ba8e1710 <SRPrint...>
0x7ff6ba8e17f4 lea -0x50(%rbp),%rax
0x7ff6ba8e17f8 mov %rax,%rcx
0x7ff6ba8e17fb call 0x7ff6ba8ef1c0 <...::~basic_string()>
0x7ff6ba8e1837 mov %rax,%rbx
0x7ff6ba8e183a lea -0x50(%rbp),%rax
0x7ff6ba8e183e mov %rax,%rcx
0x7ff6ba8e1841 call 0x7ff6ba8ef1c0 <std::...~basic_string()>
0x7ff6ba8e1846 mov %rbx,%rax
;18 : SVPrint("Ein Text");
0x7ff6ba8e1801 lea 0x1bae7(%rip),%rdx # 0x7ff6ba8fd2ef
0x7ff6ba8e1808 lea -0x20(%rbp),%rax
0x7ff6ba8e180c mov %rax,%rcx
0x7ff6ba8e180f call 0x7ff6ba8ec670 <...;::basic_string_view(char const*)>
0x7ff6ba8e1814 mov -0x20(%rbp),%rax
0x7ff6ba8e1818 mov -0x18(%rbp),%rdx
0x7ff6ba8e181c mov %rax,-0x60(%rbp)
0x7ff6ba8e1820 mov %rdx,-0x58(%rbp)
0x7ff6ba8e1824 lea -0x60(%rbp),%rax
0x7ff6ba8e1828 mov %rax,%rcx
0x7ff6ba8e182b call 0x7ff6ba8e1756 <SVPrint(...)>
;19 : }
Der Einsatz eines string_views bietet also sich immer dann an, wenn eine Funktion als Parameter einen C-String oder String erwartet der in der Funktion nicht verändert wird.
Der Zugriff auf Zeichen im string_view erfolgt wie beim string über den Indexoperator [].
Der Datentyp string_view besitzt weitere Methoden, um z.B. einen Teilstring in einem string-Objekt zu finden. Diese werden ebenfalls später behandelt.
Übungen
Sie finden die nachfolgend aufgeführten Dateien im Begleitmaterial zum Tutorial, das Sie unter https://www.cpp-tutor.de/download/Begleitmaterial_cpp23.zip runterladen können.
string1_01:
Erstellen Sie ein Programm, das die Häufigkeit der Buchstaben in einem Text ermittelt. Die Groß-/Kleinschreibung soll hierbei vernachlässigt werden. Alle Nicht-Buchstaben (Zahlen, Punkt usw.) sind gemeinsam zu erfassen.
Der zu untersuchende Text ist in der ASCII-Datei test.txt abgelegt.
Geben Sie die Häufigkeit der Buchstaben in Tabellenform aus.
Anzahl der Buchstaben in der Datei ../../04_Data/test.txt
a: 30 b: 6 c: 8 d: 19 e: 70
f: 5 g: 12 h: 15 i: 27 j: 3
k: 6 l: 15 m: 5 n: 48 o: 14
p: 2 q: 0 r: 24 s: 19 t: 18
u: 11 v: 6 w: 10 x: 0 y: 0
z: 2
Sonderzeichen: 79
string1_02:
Es ein Konvertierungsprogramm zu schreiben, das die Textdatei gcc.mak einliest und entsprechend den nachfolgenden Regeln konvertiert. Das Ergebnis ist in der Datei conv.mak abzulegen.
Die Konvertierung soll wie folgt erfolgen:
- Alle Zeilen, die mit dem Kommentarzeichen '#' beginnen, sind zu entfernen.
- Ebenso sind Leerzeilen zu entfernen.
- Alle Kleinbuchstaben sind in Großbuchstaben umzuwandeln.
Für die Konvertierung eines Kleinbuchstabens in einen Großbuchstaben ist die Bibliotheksfunktion
int std::toupper(int nC);
zu verwenden. Sie erhält das zu konvertierende Zeichen im Parameter nC übergeben und liefert als Returnwert das konvertierte Zeichen. Binden Sie für toupper zusätzlich die Header-Datei cctype ein.
Konvertierung abgeschlossen!
Inhalt der Datei conv.mak nach erfolgter Konvertierung:
CC = GCC
CXX = GCC
LIB_BASENAME = LIBSTLPORT_GCC
LINK=AR CR
DYN_LINK=GCC -SHARED -O
OBJEXT=O
DYNEXT=SOCOMP=GCC$(ARCH)
ALL: ALL_DYNAMIC ALL_STATIC
INCLUDE COMMON_MACROS.MAK
WARNING_FLAGS= -WALL -W -WNO-SIGN-COMPARE
String-Konvertierungen
String in numerische Werte
Für die Konvertierung von in Textform vorliegenden Daten in ihre numerische Werte stehen mehrere Verfahren zur Verfügung. Wir sehen uns das effizienteste Verfahren dazu an, die Konvertierung mittels der Bibliotheksfunktion from_chars(). from_chars() ist in der Header-Datei charconv definiert und liegt wiederum im Namensraum std.
Wenn Sie sich die anderen Verfahren ansehen wollen, suchen Sie auf https://en.cppreference.com nach std::from_chars(). Ganz unten auf der Seite sind ebenfalls die anderen Verfahren aufgeführt.
Integer-Konvertierung
Die Syntax, um ein Integer-Datum in einem String in seinen numerischen Wert zu konvertieren, ist wie folgt:
std::from_chars_result
std::from_chars(const char* first,
const char* last,
DTYP& value, int base = 10);
first verweist auf den Beginn des zu konvertierenden Datums und last auf dessen Ende. Das Ergebnis der Konvertierung wird in der Variablen value abgelegt. Und je nach Datentyp DTYP der Variable wird der Stringinhalt in einen entsprechenden Integer-Wert konvertiert, z.B. in einen int- oder unsigned long-Wert. Der letzte Parameter base gibt an, zu welcher Basis der Inhalt des Strings zu interpretieren ist. Zulässig sind Werte zwischen 2 und 36.
Als Rückgabe liefert from_chars() eine Struktur (Strukturen wird später erklärt) vom Datentyp from_chars_result mit den Eigenschaften ec und ptr. Bei erfolgreicher Konvertierung enthält die Eigenschaft ec den Wert std::errc() und im anderen Fall einen Fehlercode. Und die Eigenschaft ptr enthält einen Verweis auf die nächste, nicht-konvertierte Position innerhalb des Strings.
Wie mehrere in einem String enthaltenen Daten konvertiert werden, ist im nachfolgenden Beispiel dargestellt.
#include <print>
#include <string>
#include <charconv>
using namespace std::string_literals;
int main()
{
// String mit dezimal, hex und binaer Werten
std::string toConv{ "123 FFFF 10000000"s };
// Variablen fuer die Ergebnisse der Konvertierung
int iVal;
unsigned long lVal;
unsigned char cVal;
// Erste 3 Digits im String in int-Wert wandeln
auto res = std::from_chars(toConv.c_str(),
toConv.c_str() + 3, iVal);
// Pruefen, ob Konvertierung erfolgreich und
// konvertierten Wert ausgeben
if (res.ec == std::errc())
std::print("{}, ", iVal);
else
{
std::println("Fehler beim Wandeln in int-Wert");
exit(1);
}
// "Hex-String" (4 Digits) nach unsigned long
// Start und Ende werden ueber den von from_char()
// zurueckgelieferten Zeiger definiert, wobei das
// Leerzeichen uebersprungen wird
res = std::from_chars(res.ptr + 1, res.ptr + 5, lVal, 16);
// Pruefen und ausgeben des Wertes
if (res.ec == std::errc())
std::print("{}, ", lVal);
else
{
std::println("\nFehler beim Wandeln in ulong-Wert");
exit(2);
}
// "Binaer-String" (8 Digits) nach unsigned char
res = std::from_chars(res.ptr + 1, res.ptr + 9, cVal, 2);
// Pruefen und ausgeben des Wertes
if (res.ec == std::errc())
std::println("{:d}", cVal);
else
std::println("\nFehler beim Wandeln in uchar-Wert");
}
123, 65535, 128
Ein zu konvertierender 'Hex-String' darf kein Präfix 0x oder 0X als Kennzeichnung enthalten.
Gleitkomma-Konvertierung
Für die Konvertierung eines in Textform vorliegenden Gleitkommadatums in einen numerischen Wert wird ebenfalls die Funktion from_chars() verwendet, jedoch mit abweichenden Parametern. Die Funktion besitzt folgende Deklaration:
std::from_chars_result
std::from_chars(const char* first,
const char* last, DTYP& value,
std::chars_format fmt = std::chars_format::general);
first und last zeigen wieder auf den zu konvertierenden Bereich und DTYP ist einer der Gleitkomma-Datentypen. fmt bestimmt, wie der zu konvertierende String zu interpretieren ist. In der Regel kann dieser Parameter beim Aufruf entfallen, d.h., die Funktion wird mit dem Argument chars_format::general aufgerufen. Als Rückgabe liefert die Funktion wieder eine Struktur from_chars_result.
#include <print>
#include <string>
#include <charconv>
using namespace std::string_literals;
int main()
{
std::string sf1{ "12.34e-01"s }; // Gleitkomma-Strings
std::string sf2{ "3.1416"s };
// Variablen fuer das Ergebnis der Konvertierung
float fVal;
double dVal;
// Strings konvertieren
auto res = std::from_chars(sf1.c_str(),
sf1.c_str() + sf1.size(), fVal);
if (res.ec == std::errc())
std::println("{}",fVal);
res = std::from_chars(sf2.c_str(),
sf2.c_str() + sf2.size(), dVal);
if (res.ec == std::errc())
std::println("{}",dVal);
}
1.234
3.1416
Alternative Konvertierung mittels istringstream
Um mehrere Daten in einem String in einer Anweisung zu konvertieren, kann alternativ der Stream istringstream verwendet werden, der in der Header-Datei sstream definiert ist. Diese Konvertierung ist von der Laufzeit her langsamer als die Konvertierung mittels from_chars(), jedoch weniger fehleranfällig da die Positionen der Daten im String nicht beachtet werden müssen.
Für die Konvertierung ist zunächst ein istringstream-Objekt zu definieren. Diesem Objekt wird mit der Methode str() der zu konvertierende String übergeben. Und ab diesem Zeitpunkt verhält sich istringstream genauso wie der Eingabestream cin, d.h. mithilfe des Operators >> werden die einzelnen Wörter bzw. Werte aus dem Stream extrahiert.
#include <print>
#include <string>
#include <sstream>
using namespace std::string_literals;
int main()
{
// Streamobjekt definieren
std::istringstream is;
// string definieren und initialisieren
auto s1 = "123 0xffff 3.4"s;
// String dem Streamobjekt übergeben
is.str(s1);
// Variablen für die Konvertierung definieren
int intVar;
unsigned long ulVar;
double dblVar;
// Werte extrahieren
// Beachte die Umstellung auf den Hex-Wert
is >> intVar >> std::hex >> ulVar >> std::dec >> dblVar;
std::println("int: {}, ulong: {}, double: {}",intVar, ulVar, dblVar);
}
int: 123, ulong: 65535, double: 3.4
Alternativ kann das istringstream-Objekt auch bei seiner Definition mit einen (C-)String initialisiert werden.
Soll der Inhalt mehrerer Strings mit demselben istringstream-Objekt konvertiert werden, ist nach der Auswertung eines Strings die Methode clear() des istringstream-Objekts aufzurufen, um das Streamobjekt zurückzusetzen.
#include <print>
#include <string>
#include <sstream>
using namespace std::string_literals;
int main()
{
// Streamobjekt definieren und initialisieren
std::istringstream is("12 13"s);
// String konvertieren
int var1, var2;
is >> var1 >> var2;
std::println("var1: {}, var2: {}",var1,var2);
// Status des Streamobjekts zurücksetzen
is.clear();
// Neuen C-String dem Streamobjekt zuweisen
is.str("22 33");
is >> var1 >> var2;
std::println("var1: {}, var2: {}",var1,var2);
}
var1: 12, var2: 13
var1: 22, var2: 33
Numerische Werte in einen C-String
Integer-Konvertierung
Um einen numerischen Wert in einen C-String umzuwandeln, kann die Funktion to_chars() angewandt werden, welche ebenfalls in der Header-Datei charconv und im Namensraum std definiert ist.
std::to_chars_result
std::to_chars(char* first, char* last,
DTYP value, int base = 10);
first verweist auf den Beginn des Bereichs, in dem der konvertierte Wert abgelegt wird, last auf dessen Ende und value enthält den zu konvertierenden Wert. Der letzte Parameter base legt fest, zu welcher Basis der Wert konvertiert wird. Zulässig sind wieder Werte zwischen 2 und 36.
Als Rückgabe liefert to_chars() ebenfalls eine Struktur, jetzt vom Datentyp to_chars_result. War die Konvertierung erfolgreich, enthält das Strukturelement ec den 'Wert' std::err() und das Strukturelement ptr verweist auf die nächste nicht für die Konvertierung verwendete Position im C-String.
Der C-String mit dem konvertierten Wert wird nicht mit einer 0 abgeschlossen!
Gleitkomma-Konvertierung
Auch für diese Konvertierung wird die Bibliotheksfunktion to_chars() verwendet.
std::to_chars_result
std::to_chars(char* first, char* last,
DTYP value);
first und last verweisen wieder auf den Bereich, in dem der konvertierte Wert abgelegt wird und value ist das zu konvertierende Gleitkommadatum.
Von dieser Funktion gibt es zwei weitere Varianten, mit denen das Format und die Anzahl der Stellen für die Konvertierung definiert werden können.
#include <print>
#include <charconv>
int main()
{
// Zu konvertierende Werte
short sVal = 0x55;
unsigned long lVal = 2'345'000UL;
float fVal = 123.456f;
// char-Feld zur Aufnahme des Strings
constexpr int SIZE = 80;
char conv[SIZE];
// short-Wert nach C-String
// Konvertierung in einen 'Binaerstring' (Basis 2)
std::to_chars_result res;
if (res = std::to_chars(conv, conv + SIZE, sVal, 2);
res.ec != std::errc())
std::println("Fehler Konvertierung {}", sVal);
// long-Wert nach C-String
// Konvertierung wird an vorherige angehaengt
// und durch Komma getrennen
*(res.ptr) = ',';
if (res = std::to_chars(res.ptr+1, conv + SIZE, lVal);
res.ec != std::errc())
std::println("Fehler Konvertierung {}", lVal);
// float-Wert nach C-String
// Konvertierung wird an vorherige angehaengt
// Konvertierungen durch Zeilenvorschub trennen
*(res.ptr) = '\n';
if (res = std::to_chars(res.ptr + 1, conv + SIZE, fVal);
res.ec != std::errc())
std::println("Fehler Konvertierung {}\n", fVal);
// C-String mit 0 abschliessen!!!
*(res.ptr) = 0;
std::println("{}",conv);
}
1010101,2345000
123.456
Numerische Werte in einen String
Um numerischen Daten in einen String zu konvertieren, wird die Funktion std::format() verwendet (siehe Kapitel Standardausgabe). Sie liefert ein string-Objekt mit den konvertierten Werten zurück.
#include <iostream>
#include <format>
#include <string>
int main()
{
// Zu konvertierende Werte
short sVal = 0x55;
unsigned long lVal = 2'345'000UL;
float fVal = 123.456f;
// short-Wert nach String
// Konvertierung in einen 'Binaerstring' (Basis 2)
std::string out = std::format("{:#b}, ", sVal);
// long-Wert nach String
out += std::format("{}\n", lVal);
// float-Wert nach String
out += std::format("{:.2f}\n", fVal);
// String ausgeben
std::cout << out;
}
0b1010101, 2345000
123.46
Alternativ kann für die Konvertierung von Werten in einen String noch der Stream ostringstream verwendet werden. Da er aber keine zusätzliche Funktionalität gegenüber std::format() bietet, wird er nicht weiter betrachtet.
String aufteilen
Um einen String in einzelne Wörter aufzuteilen, kann der erwähnte Stream istringstream verwendet werden. Nachdem dem istringstream-Objekt ein String zugewiesen wurde, wird so lange aus dem istringstream ausgelesen, bis dessen Methode eof() true zurückliefert. Die einzelnen ausgelesenen Wörter werden dabei nacheinander mittels des Operators >> in ein string-Objekt übertragen.
#include <string>
#include <sstream>
#include <print>
#include <iostream>
int main()
{
// Stringobjekte definieren
std::string line, word;
std::print("Bitte eine Zeile eingeben: ");
// Komplette Zeile von der Standard-Eingabe einlesen
std::getline(std::cin,line);
// Eingabe an istringstream uebergeben
std::istringstream is(line);
// Eingabe in einzelne Woerter zerlegen
while (!is.eof())
{
// Einzelnes Wort aus Eingabe-String extrahieren
is >> word;
std::println("{}",word);
}
}
Bitte eine Zeile eingeben: Teile diesen Text auf
Teile
diesen
Text
auf
Mithilfe des istringstream können ebenfalls Teile eines Strings, die durch ein definiertes Trennzeichen voneinander getrennt sind, extrahiert werden. Der 'Trick' ist dabei folgender:
Die Funktion getline() wurde bisher nur zum Einlesen einer kompletten Zeile von der Standardeingabe verwendet. Doch die Funktion kann wesentlich mehr. getline() kann aus einem beliebigen Input-Stream eine komplette Zeile einlesen. D.h., die Funktion getline() kann sowohl vom Eingabestream cin wie auch vom Dateistream ifstream oder vom istringstream Stream eine Zeile einlesen.
Aber es soll ja nicht die komplette Zeile in einem String abgelegt werden, sondern nur der Teil bis zum ersten definierten Trennzeichen. Und dazu wird an getline() in einem dritten Parameter das Trennzeichen als char übergeben. Standardmäßig ist das Trennzeichen der Zeilenvorschub '\n'.
#include <string>
#include <sstream>
#include <print>
using namespace std::string_literals;
int main()
{
// Zu untersuchender String, Trennzeichen ist Komma
const auto LINE =
"Agathe Mueller,Gansweg 23,12345 ADorf"s;
// 3 string-Objekte definieren
std::string name, street, city;
// String in einen istringstream übertragen
std::istringstream is(LINE);
// Aus istringstream einlesen mit Komma als Trennzeichen
std::getline(is,name,',');
// Weiter einlesen bis zum nächsten Trennzeichen
std::getline(is,street,',');
// Nun den Rest einlesen
std::getline(is,city);
// Strings ausgeben
std::println("Name: {}\nStrasse: {}\nOrt: {}",
name,street,city);
}
Name: Agathe Mueller
Strasse: Gansweg 23
Ort: 12345 ADorf
Übungen
sconv_01:
Vorgegeben ist eine Datei adresse.csv mit folgendem Aufbau:
Vorname;Nachname;E-Mail;Strasse;Wohnort
Schreiben Sie ein Programm, dass die Namen und die dazugehörige E-Mail Adressen aus der Datei ausliest und ausgibt.
Name: Xaver Xelsbrot, E-Mail: Xaver@speedy.de
Name: Gustav Gans, E-Mail: guga@entennet.com
Name: Daisy Duck, E-Mail: ducky@entennet.com
Name: Emilie Lehmann, E-Mail: emmi@world.de
sconv_03:
Etwas aus dem richtigen Leben: Die Datei swr1_hitparade.csv enthält (fast) alle Musiktitel ab der Position 1000, die in der SWR1-Hitparade seit 1989 vertreten waren. Die Datei hat folgenden prinzipiellen Aufbau:
Titel;Interpret;1989;1990;...;2025
dreadlock holiday;10cc;419;896;...;0
eagle;abba;0;0;...;436
Die erste Zeile ist die Titelzeile mit den Spaltenüberschriften, d. h., die erste Spalte enthält den Titel, die zweite den Interpreten und anschließend folgen 26 Spalten mit den Jahreszahlen (einige Jahre wurden übersprungen).
Danach folgen die Zeilen mit den entsprechenden Hitparadeneinträgen. Beachten Sie, dass jeweils der letzte Wert in einer Zeile nicht mit einem Semikolon abgeschlossen ist.
Ihre Aufgabe ist es, die ersten 15 Songs einzulesen und die jeweilige maximale Position sowie das dazugehörige Jahr zu ermitteln. Beachten Sie, dass die Position 1 die höchste Position in der Hitparade ist!
Geben Sie die Interpreten, die Songtitel und die dazugehörige maximale Poistion in der Hitparade aus.
Verwenden Sie keinen istringstream zur Konvertierung der string-Daten in numerische Werte!
fantastischen vier mit '25', Pos:667(2015)
lo & leduc mit '79', Pos:486(2021)
james blunt mit '1973', Pos:132(2007)
prince mit '1999', Pos:394(1989)
bap mit '10. Jun', Pos:686(1990)
blue oeyster cult mit '(don't fear) the reaper', Pos:477(2017)
ub40 mit '(i can't help) falling in love', Pos:236(1994)
bon jovi mit '(you want to) make a memory', Pos:529(2007)
toten hosen mit '1000 gute gruende', Pos:104(1990)
e.t. mit '12sati', Pos:881(1994)
schweizer mit '13 tage', Pos:540(2004)
skid row mit '18 and life', Pos:128(1990)
mark forster mit '194 laender', Pos:158(2020)
rolling stones mit '2000 lightyears from home', Pos:912(1990)
green day mit '21 guns', Pos:129(2009)