Einsatz von Iteratoren
Content |
Wenn eine neue Datenstruktur mit Inhalten aus einer anderen Datenstruktur zu füllen ist, dann ist es gut, wenn wir ebenfalls auf den Einsatz des default constructor verzichten können. Stattdessen sollte sogleich der Kopierkonstruktor zum Zuge kommen. Naheliegend ist es hier, einen Konstruktor anzubieten, der ein Paar von Iteratoren akzeptiert.
So könnte eine Verwendung aussehen:
#include "array.hpp" int main() { int values[] = {2, 3, 5, 7, 11, 13}; std::size_t dim = sizeof(values)/sizeof(values[0]); Array<int> a(values, values + dim); }
Da wir beim Array die Dimensionierung sogleich festlegen müssen, geht dies nur, wenn die Iteratoren den Bedingungen eines random access iterator genügen, d.h. mit der Differenz zweier Zeiger könnten wir sogleich die Zahl der Elemente bestimmen.
Alternativ könnte auch die generische Funktion std::distance(it1, it2) aus #include <iterator> verwendet werden, die bei random access iterators konstanten Aufwand hat, im allgemeineren Fall von Forward-Iteratoren einen linearen Aufwand benötigt.
Ebenfalls wäre es reizvoll, wenn die Elemente eines Arrays mit Iteratoren durchlaufen werden könnten. Hierzu werden die Methoden begin und end benötigt. In Fällen wie diesen wäre es sogar besonders einfach, da reguläre Zeiger verwendet werden können und somit der Aufwand entfällt, eine spezielle Iterator-Klasse zu entwickeln. Wie bei den Zugriffsoperatoren ist dann aber sowohl der Fall mit und ohne Schreibschutz zu berücksichtigen.
Aufgaben und Fragen
-
Fügen Sie einen wie oben beschriebenen Konstruktor hinzu, der als Parameter ein Iteratoren-Paar erhält und der alle Elemente des Arrays kopierkonstruiert aus der von den Iteratoren spezifizierten Datenbestand. Folgendes Testprogramm sollte dann funktionieren:
#include <iostream> #include "array.hpp" int main() { int values[] = {2, 3, 5, 7, 11, 13}; std::size_t dim = sizeof(values)/sizeof(values[0]); Array<int> a(values, values + dim); for (std::size_t index = 0; index < a.get_size(); ++index) { std::cout << " " << a(index); } std::cout << std::endl; }
Was erwarten Sie als Ausgabe bei folgendem Testprogramm, wenn es wie folgt aufgerufen wird:
#include <iostream> #include <iterator> #include "array.hpp" int main() { std::istream_iterator<int> it(std::cin); std::istream_iterator<int> end; Array<int> a(it, end); for (std::size_t index = 0; index < a.get_size(); ++index) { std::cout << " " << a(index); } std::cout << std::endl; }
g++ -Wall -o test2 test2.cpp echo 1 2 3 | ./test2
Denken Sie kurz darüber nach, bevor Sie es ausprobieren. Und wenn Sie nicht darauf gekommen sind, denken Sie nach, woran es liegt und wie Sie mit diesem Fall umgehen könnten. Um initialisierte von uninitialisierten Werten sauber unterscheiden zu können, hilft vielleicht auch noch folgendes Testprogramm, das den Wert 42 für per default constructor konstruierte Elemente nimmt:
#include <iostream> #include <iterator> #include "array.hpp" struct Integer { Integer() : value(42) { } int value; }; std::istream& operator>>(std::istream& in, Integer& i) { return in >> i.value; } std::ostream& operator<<(std::ostream& out, Integer i) { return out << i.value; } int main() { std::istream_iterator<Integer> it(std::cin); std::istream_iterator<Integer> end; Array<Integer> a(it, end); for (std::size_t index = 0; index < a.get_size(); ++index) { std::cout << " " << a(index); } std::cout << std::endl; }
Wie auch immer Sie das Problem lösen, sollten Sie darauf achten, dass es auch ohne default constructor klappt:
#include <iostream> #include "array.hpp" struct Integer { Integer(int value) : value(value) { } int value; }; std::ostream& operator<<(std::ostream& out, Integer i) { return out << i.value; } int main() { Integer values[] = {2, 3, 5, 7, 11, 13}; std::size_t dim = sizeof(values)/sizeof(values[0]); Array<Integer> a(values, values + dim); for (std::size_t index = 0; index < a.get_size(); ++index) { std::cout << " " << a(index); } std::cout << std::endl; }
-
Fügen Sie wie oben beschrieben die Methoden begin und end hinzu, wobei Sie wie beim Zugriffsoperator beide Varianten unterstützen sollten. Ein erstes Testprogramm hierzu, das eine auf Iteratoren basierende for range loop verwendet:
#include <iostream> #include "array.hpp" int main() { int values[] = {2, 3, 5, 7, 11, 13}; std::size_t dim = sizeof(values)/sizeof(values[0]); Array<int> a(values, values + dim); for (auto value: a) { std::cout << " " << value; } std::cout << std::endl; }
Funktioniert bei Ihnen dann auch folgender Test?
#include <iostream> #include "array.hpp" template<typename T> void print_array(const Array<T>& a) { for (auto value: a) { std::cout << " " << value; } std::cout << std::endl; } int main() { int values[] = {2, 3, 5, 7, 11, 13}; std::size_t dim = sizeof(values)/sizeof(values[0]); Array<int> a(values, values + dim); print_array(a); }
Interessant wird es, wenn Sie auf die Elemente mit so einer Schleife schreibenderweise zugreifen möchten. Kann folgender Versuch zum Initialisieren eines Arrays funktionieren? Wenn nein, woran liegt es und wie kann das korrigiert werden?
#include <iostream> #include "array.hpp" int main() { Array<int> a(3); for (auto value: a) { value = 42; } for (auto value: a) { std::cout << " " << value; } std::cout << std::endl; }