Strumień, który mieszka w napisie
Znasz już cin i cout: strumienie podłączone do klawiatury i ekranu. Strumień napisu to ta sama idea, tylko dane mieszkają w std::string w pamięci. Piszesz do niego przez << i wyciągasz wartości przez >>, dokładnie jak przy strumieniach konsoli, ale nic nie trafia do terminala.
Ta jedna idea rozwiązuje dwa codzienne problemy: parsowanie tekstu na wartości określonych typów i budowanie sformatowanego napisu z różnych kawałków. Wszystko znajduje się w nagłówku <sstream> i występuje w trzech odmianach:
istringstream: tylko do odczytu, do parsowania wejścia.ostringstream: tylko do zapisu, do budowania wyjścia.stringstream: w obu kierunkach.
ss.str() zwraca wszystko, co do tej pory zapisano, jako zwykły string. Operatory << wykonały to samo formatowanie int na tekst co cout, tylko wynik został przechwycony, a nie wypisany.
Parsowanie: wyciąganie wartości określonych typów
Prawdziwa moc ujawnia się przy >>. Daj strumieniowi trochę tekstu, a wyodrębnianie zamieni każdy token na typ, o który prosisz, pomijając białe znaki między nimi. To czysty sposób na rozbicie jednej linii na kilka pól o określonych typach:
Każde >> czyta do następnego białego znaku i konwertuje: "Ada" na string, "36" na int, "5.5" na double. Zwróć uwagę na istringstream: typ tylko do odczytu jasno pokazuje, że wyłącznie parsujesz.
Bezpieczna konwersja napisów na liczby
Częste zadanie to zamiana pojedynczego napisu, takiego jak "42", na int. stringstream to potrafi i mówi, czy tekst był w ogóle poprawną liczbą, czego atoi nigdy nie robi:
Tutaj ss >> value czyta 123, zatrzymuje się na a i kończy się sukcesem, więc jeśli to ważne, zawsze sprawdzaj cały napis. Solidne sprawdzenie polega na wczytaniu liczby i upewnieniu się, że nie zostało nic istotnego. Przy prostych konwersjach pojedynczej wartości std::stoi, std::stod i pokrewne są krótsze; po stringstream sięgaj, gdy jeden napis niesie kilka różnych wartości.
Dzielenie napisu według separatora
Dzielenie tekstu to najczęściej wyszukiwane zadanie ze strumieniami napisów. Połącz strumień z std::getline, które potrafi czytać do dowolnego znaku separatora, a nie tylko do znaku nowej linii:
Wzorzec while (getline(...)) wykonuje pętlę, dopóki strumień nie wyschnie, bo getline zwraca strumień, a strumień daje false, gdy nic już nie zostało. Każdy token trafia do wektora, naturalnego miejsca na zbieranie zmiennej liczby kawałków. Pomiń trzeci argument, a zamiast tego podzielisz tekst na tokeny po białych znakach przez >>: wybierz separator, którego używają twoje dane.
Budowanie napisów w pamięci
W drugą stronę ostringstream składa sformatowany napis bez ryzyka przepełnienia bufora jak przy sprintf i bez kruchego ręcznego łączenia. Idealnie nadaje się do linii logów, nazw plików czy komunikatów zszytych z liczb i tekstu:
Całe formatowanie z <iomanip>, którego używasz z cout, czyli setw, setfill, setprecision, hex, działa identycznie na strumieniu napisu, więc wynik z dopełnieniem, o stałej szerokości albo szesnastkowy trafia prosto do string.
Pułapka: zresetuj przed ponownym użyciem
Pułapka, na którą trafia każdy, to ponowne użycie strumienia bez resetu. Gdy strumień dojdzie do końca danych, jego flagi eof i fail pozostają ustawione, a każde kolejne >> po cichu nic nie robi: żadnego błędu, tylko nieaktualne wartości:
stringstream ss("10");
int a, b;
ss >> a; // a = 10, strumień jest na końcu -> flaga eof ustawiona
ss.str("20"); // wczytaj nowy tekst...
ss >> b; // PO CICHU SIĘ NIE UDAJE: flagi błędu są nadal ustawione, b bez zmian
Musisz wyczyścić stan błędu i zawartość. Właściwa kolejność to najpierw clear(), potem str():
Dwa pokrewne błędy: ss.clear() czyści flagi, ale nie opróżnia bufora (do tego służy ss.str("")), a stringstream, do którego ciągle dopisujesz przez <<, będzie rósł bez końca. Twórz nowy strumień wewnątrz pętli, zamiast używać tego samego, zabrudzonego w każdej iteracji.
Dalej: tablice
Strumienie napisów dają czysty sposób zamiany tekstu na wartości określonych typów i z powrotem, często na całą listę takich wartości, jak vector wypełniany przy dzieleniu CSV. Aby przechowywać i indeksować stałe kolekcje takich wartości, potrzebujesz najbardziej podstawowego kontenera w języku. Dalej omówimy tablice: jak je deklarować, indeksować, przechodzić po nich pętlą i omijać dostęp poza zakresem, który powoduje tyle niezdefiniowanego zachowania w C++.
Najczęściej zadawane pytania
Czym jest stringstream w C++?
std::stringstream to strumień oparty na string zamiast na klawiaturze czy pliku. Piszesz do niego przez << i czytasz z niego przez >>, tak jak przy cout i cin, dlatego to standardowe narzędzie do parsowania tekstu i budowania napisów w pamięci. Znajduje się w nagłówku <sstream>.
Jak w C++ zamienić string na int przez stringstream?
Umieść tekst w strumieniu i wyodrębnij go do liczby: stringstream ss("42"); int n; ss >> n;. Sprawdź if (ss) (albo if (ss >> n)), żeby potwierdzić, że konwersja naprawdę się udała. W prostych przypadkach std::stoi jest krótsze, ale stringstream błyszczy, gdy jeden napis zawiera kilka różnych wartości.
Dlaczego trzeba wywołać clear() na stringstream?
Gdy strumień dojdzie do końca danych, jego bity eof/fail zostają ustawione i każde kolejne >> po cichu nic nie robi. Jeśli używasz tego samego stringstream z nową zawartością, wywołaj ss.clear(), żeby wyczyścić flagi błędu, a potem ss.str(newText), żeby wczytać świeże dane. Inaczej odczyty po cichu się nie udają.