Menu

Strumienie napisów w C++: stringstream do parsowania i budowania

Jak używać std::stringstream, istringstream i ostringstream do parsowania tekstu, dzielenia napisów po białych znakach, konwersji między napisami a liczbami i budowania sformatowanych napisów w pamięci.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

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ą.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ