Iteratory strumienia w C++
• tech • 598 słów • 3 minuty czytania
Pisałem o numeric_limits jako zapomnianej części biblioteki numerycznej, dziś do tego “worka” mogę dorzucić iteratory strumieni jako rzadko używane “twory”. Niektórych może zdziwić ten fakt, że strumienie także posiadają iteratory, a ich wykorzystanie może bardzo nam pomoc i uprościć kod.
Na początek najlepiej zapoznać się z tym co mówi dokumentacja na temat istream_iterator i ostream_iterator z nagłówka iterator.
Często iteratory te są wykorzystywane w różnych przykładach innych algorytmów do szybkiego pobierania danych z strumienia wejściowego i wyświetlania na wyjściowym, w produkcyjnym oprogramowaniu rzadko je można spotkać.
Nasz prosty programik będzie pobierał dane z strumienia wejściowego i umieszczał je w kontenerze, a następnie na tych danych będziemy wykonywać jakieś operacje, po czym na koniec “wypluwać” wynik na wyjściowy strumień.
Z pomocą iteratorów strumieni i algorytmów biblioteki standardowej, łatwo możemy zaimplementować obsługę strumieni - pobieranie/wysyłanie danych z/do wektora. W naszym przypadku moglibyśmy wykorzystać algorytm std::copy (z nagłówka algorithm
) i funkcje te przybrałyby bardzo prostą postać:
inline void get_data(std::istream& from, std::vector<std::string>& to) {
std::copy(std::istream_iterator<std::string>(from),
std::istream_iterator<std::string>(),
std::back_inserter(to));
}
inline void put_data(std::vector<std::string>& from, std::ostream& to) {
std::copy(from.begin(), from.end(),
std::ostream_iterator<std::string>(to, "\n"));
}
Nasz programik sortujący wprowadzone słowa:
int main() {
std::vector<std::string> vec;
get_data(std::cin, vec);
std::sort(vec.begin(), vec.end());
std::cout << std::endl << "Sorted:" << std::endl;
put_data(vec, std::cout);
return 0;
}
Nic nie stoi na przeszkodzie, aby podobnie operować na strumieniach plikowych - fstream.
Warto jeszcze wspomnieć, że istnieją podobne iteraotry dla buforów strumieniowych - istreambuf_iterator
i ostreambuf_iterator
.
Bardzo przydatne może się okazać narzędzie służące do szybkiego zapisywania do strumienia wyjściowym zawartości kontenera, tak jak chociażby w wyżej zdefiniowanej funkcji put_data
.
Małą niedogodnością jest tutaj wypisywanie niepotrzebnego na końcu delimitera, ale można się tego łatwo pozbyć.
Poniżej prosta funkcja do “wypluwania” zawartości dowolnego kontenera.
template<typename ContainerType>
inline void put_data(std::ostream& os, ContainerType& con, const std::string& delim) {
typename ContainerType::const_iterator last = con.end();
std::advance(last, -1);
if (con.size() > 1)
std::copy(con.begin(), last,
std::ostream_iterator<typename ContainerType::value_type>(os, delim.c_str()));
if (con.size() > 0)
os << *last;
}
Funkcja działa bezproblemowo dla kontenerów przechowujących proste typy, a dokładniej takie, które posiadają zdefiniowany operator « dla strumienia wyjściowego z naszym typem, dla pozostałych typów, zdefiniowanych przez użytkownika, musimy zadbać o takowy operator.
Podobnie, aby funkcja współdziałała z kontenerów przechowujących pary powiązanych ze sobą kluczy i wartości, tak jak std::map niezbędne jest dodanie dodatkowego mechanizmu do prezentacji takich danych, najlepiej chyba w postaci:
cppklucz - wartość
Mechanizmem tym jest wspomniany operator « dla danej pary:
template<class T, class U>
std::ostream& operator<<(std::ostream& os, const std::pair<T, U>& p) {
return os << p.first << " - " << p.second;
}
Dla poprawnego działania, operator ten powinien zostać zdefiniowany w przestrzeni nazw std
.
I na koniec prosty programik prezentujący i testujący działanie naszego mechanizmu.
Dla prostoty zdefiniujmy sobie dodatkowe operatory « strumienia wyjściowego z używanymi kontenerami:
std::ostream& operator<<(std::ostream& os, const std::vector<int>& vec) {
put_data(os, vec, ", ");
return os;
}
std::ostream& operator<<(std::ostream& os, const std::map<int, int>& vec) {
put_data(os, vec, ", ");
return os;
}
A program przyjmie prostą postać:
int main() {
std::vector<int> vec(10);
int array[] = { 1, 2, 3, 4, 5 };
vec.assign(array, array + sizeof(array) / sizeof(int));
std::cout << "vector sized at " << vec.size() << ": " << vec << std::endl;
vec.resize(1);
std::cout << "vector sized at " << vec.size() << ": " << vec << std::endl;
vec.clear();
std::cout << "vector sized at " << vec.size() << ": " << vec << std::endl;
std::map<int, int> map;
map[0] = 1;
map[1] = 2;
map[2] = 3;
std::cout << "map sized at " << map.size() << ": " << map << std::endl;
return 0;
}
Myślę, że łatwo można zauważyć to, że iteratory strumieni, w niektórych sytuacjach mogą być bardzo pomocne i nieraz mogą ułatwić nam pracę.
Komentarze (0)