C++
Strumienie C ++
Szukaj…
Uwagi
Domyślny konstruktor std::istream_iterator
konstruuje iterator, który reprezentuje koniec strumienia. Zatem std::copy(std::istream_iterator<int>(ifs), std::istream_iterator<int>(), ....
oznacza kopiowanie z bieżącej pozycji w ifs
do końca.
Strumienie ciągów
std::ostringstream
to klasa, której obiekty wyglądają jak strumień wyjściowy (tzn. można do nich pisać za pomocą operator<<
), ale w rzeczywistości przechowują wyniki zapisu i udostępniają je w postaci strumienia.
Rozważ następujący krótki kod:
#include <sstream>
#include <string>
using namespace std;
int main()
{
ostringstream ss;
ss << "the answer to everything is " << 42;
const string result = ss.str();
}
Linia
ostringstream ss;
tworzy taki obiekt. Ten obiekt jest najpierw manipulowany jak zwykły strumień:
ss << "the answer to everything is " << 42;
Jednak po tym wynikowy strumień można uzyskać w następujący sposób:
const string result = ss.str();
( result
ciągu będzie równy "the answer to everything is 42"
).
Jest to szczególnie przydatne, gdy mamy klasę, dla której zdefiniowano serializację strumienia i dla której potrzebujemy postaci ciągu. Załóżmy na przykład, że mamy klasę
class foo
{
// All sort of stuff here.
};
ostream &operator<<(ostream &os, const foo &f);
Aby uzyskać ciąg znaków reprezentujący obiekt foo
,
foo f;
moglibyśmy użyć
ostringstream ss;
ss << f;
const string result = ss.str();
Następnie result
zawiera ciąg znaków reprezentujący obiekt foo
.
Czytanie pliku do końca
Czytanie pliku tekstowego wiersz po wierszu
Właściwy sposób czytania pliku tekstowego linia po linii do końca zwykle nie wynika z dokumentacji ifstream
. Rozważmy kilka typowych błędów popełnianych przez początkujących programistów C ++ i właściwy sposób odczytu pliku.
Linie bez znaków białych znaków
Dla uproszczenia załóżmy, że każda linia w pliku nie zawiera symboli białych znaków.
ifstream
ma operator bool()
, który zwraca true, gdy strumień nie zawiera błędów i jest gotowy do odczytu. Ponadto ifstream::operator >>
zwraca odwołanie do samego strumienia, dzięki czemu możemy odczytać i sprawdzić EOF (jak również błędy) w jednym wierszu z bardzo elegancką składnią:
std::ifstream ifs("1.txt");
std::string s;
while(ifs >> s) {
std::cout << s << std::endl;
}
Linie ze znakami spacji
ifstream::operator >>
czyta strumień, aż pojawi się jakikolwiek biały znak, więc powyższy kod wydrukuje słowa z linii w osobnych wierszach. Aby przeczytać wszystko do końca linii, użyj std::getline
zamiast ifstream::operator >>
. getline
zwraca odwołanie do wątku, z którym pracował, więc dostępna jest ta sama składnia:
while(std::getline(ifs, s)) {
std::cout << s << std::endl;
}
Oczywiście std::getline
powinien być również używany do odczytu pliku jednowierszowego do końca.
Odczytywanie pliku do bufora jednocześnie
Na koniec przeczytajmy plik od początku do końca, nie zatrzymując się na żadnym znaku, w tym białych znakach i znakach nowej linii. Jeśli wiemy, że dokładny rozmiar pliku lub górna granica długości jest dopuszczalna, możemy zmienić rozmiar łańcucha, a następnie przeczytać:
s.resize(100);
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
s.begin());
W przeciwnym razie musimy wstawić każdy znak na końcu łańcucha, więc potrzebujemy std::back_inserter
:
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
std::back_inserter(s));
Alternatywnie można zainicjować kolekcję danymi strumienia, używając konstruktora z argumentami zakresu iteratora:
std::vector v(std::istreambuf_iterator<char>(ifs),
std::istreambuf_iterator<char>());
Pamiętaj, że te przykłady mają również zastosowanie, jeśli ifs
jest otwarty jako plik binarny:
std::ifstream ifs("1.txt", std::ios::binary);
Kopiowanie strumieni
Plik można skopiować do innego pliku za pomocą strumieni i iteratorów:
std::ofstream ofs("out.file");
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
std::ostream_iterator<char>(ofs));
ofs.close();
lub przekierowane do dowolnego innego rodzaju strumienia z kompatybilnym interfejsem. Na przykład strumień sieciowy Boost.Asio:
boost::asio::ip::tcp::iostream stream;
stream.connect("example.com", "http");
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
std::ostream_iterator<char>(stream));
stream.close();
Tablice
Ponieważ iteratory można uznać za uogólnienie wskaźników, kontenery STL w powyższych przykładach można zastąpić rodzimymi tablicami. Oto jak parsować liczby w tablicy:
int arr[100];
std::copy(std::istream_iterator<char>(ifs), std::istream_iterator<char>(), arr);
Uważaj na przepełnienie bufora, ponieważ nie można zmienić rozmiaru tablic w locie po ich przydzieleniu. Na przykład, jeśli powyższy kod zostanie zasilony plikiem zawierającym więcej niż 100 liczb całkowitych, spróbuje zapisać poza tablicą i zachowa się w nieokreślony sposób.
Drukowanie kolekcji za pomocą iostream
Podstawowe drukowanie
std::ostream_iterator
pozwala wydrukować zawartość kontenera STL do dowolnego strumienia wyjściowego bez wyraźnych pętli. Drugi argument konstruktora std::ostream_iterator
ustawia ogranicznik. Na przykład następujący kod:
std::vector<int> v = {1,2,3,4};
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " ! "));
wydrukuje
1 ! 2 ! 3 ! 4 !
Rzutowany typ niejawny
std::ostream_iterator
pozwala std::ostream_iterator
rzutować typ zawartości kontenera. Na przykład, dostrójmy std::cout
aby drukować wartości zmiennoprzecinkowe z 3 cyframi po przecinku:
std::cout << std::setprecision(3);
std::fixed(std::cout);
i std::ostream_iterator
instancję std::ostream_iterator
z float
, podczas gdy zawarte wartości pozostają int
:
std::vector<int> v = {1,2,3,4};
std::copy(v.begin(), v.end(), std::ostream_iterator<float>(std::cout, " ! "));
więc powyższy kod daje wynik
1.000 ! 2.000 ! 3.000 ! 4.000 !
pomimo std::vector
posiada int
s.
Generacja i transformacja
Funkcje std::generate
, std::generate_n
i std::transform
stanowią bardzo potężne narzędzie do manipulacji danymi w locie. Na przykład mając wektor:
std::vector<int> v = {1,2,3,4,8,16};
możemy łatwo wydrukować wartość logiczną instrukcji „x jest parzyste” dla każdego elementu:
std::boolalpha(std::cout); // print booleans alphabetically
std::transform(v.begin(), v.end(), std::ostream_iterator<bool>(std::cout, " "),
[](int val) {
return (val % 2) == 0;
});
lub wydrukuj kwadratowy element:
std::transform(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " "),
[](int val) {
return val * val;
});
Drukowanie N liczb losowych rozdzielanych spacjami:
const int N = 10;
std::generate_n(std::ostream_iterator<int>(std::cout, " "), N, std::rand);
Tablice
Podobnie jak w części dotyczącej czytania plików tekstowych, prawie wszystkie te kwestie można zastosować do macierzy natywnych. Na przykład wypiszmy kwadratowe wartości z natywnej tablicy:
int v[] = {1,2,3,4,8,16};
std::transform(v, std::end(v), std::ostream_iterator<int>(std::cout, " "),
[](int val) {
return val * val;
});
Parsowanie plików
Parsowanie plików do kontenerów STL
istream_iterator
s są bardzo przydatne do odczytywania sekwencji liczb lub innych analizowalnych danych do kontenerów STL bez wyraźnych pętli w kodzie.
Używając jawnego rozmiaru kontenera:
std::vector<int> v(100);
std::copy(std::istream_iterator<int>(ifs), std::istream_iterator<int>(),
v.begin());
lub z wstawieniem iteratora:
std::vector<int> v;
std::copy(std::istream_iterator<int>(ifs), std::istream_iterator<int>(),
std::back_inserter(v));
Zauważ, że liczby w pliku wejściowym mogą być podzielone przez dowolną liczbę dowolnych białych znaków i znaków nowej linii.
Analizowanie heterogenicznych tabel tekstowych
Ponieważ istream::operator>>
odczytuje tekst aż do spacji, można go używać w warunku while
do analizowania złożonych tabel danych. Na przykład, jeśli mamy plik z dwiema liczbami rzeczywistymi, po których następuje ciąg (bez spacji) w każdym wierszu:
1.12 3.14 foo
2.1 2.2 barr
można go sparsować w następujący sposób:
std::string s;
double a, b;
while(ifs >> a >> b >> s) {
std::cout << a << " " << b << " " << s << std::endl;
}
Transformacja
std::istream_iterator
zakresami std::istream_iterator
można używać dowolnej funkcji manipulowania zakresem. Jednym z nich jest std::transform
, który pozwala przetwarzać dane w locie. Na przykład, przeczytajmy wartości całkowite, pomnóż je przez 3,14 i zapisz wynik w pojemniku zmiennoprzecinkowym:
std::vector<double> v(100);
std::transform(std::istream_iterator<int>(ifs), std::istream_iterator<int>(),
v.begin(),
[](int val) {
return val * 3.14;
});