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)

Dodaj komentarz

/dozwolony markdown/

/nie zostanie opublikowany/