Zaawansowane CPP/Ćwiczenia 10: Inteligentne wskaźniki: Różnice pomiędzy wersjami
Nie podano opisu zmian |
Nie podano opisu zmian |
||
Linia 79: | Linia 79: | ||
Definujemy klasę zagnieżdżoną wewnątrz <tt>binary_tree</tt>: | Definujemy klasę zagnieżdżoną wewnątrz <tt>binary_tree</tt>: | ||
class iterator { | class iterator { | ||
std::deque<node *> _nodes; | std::deque<node *> _nodes; | ||
node *_current; | node *_current;<br> | ||
iterator(node *ptr <nowiki> =</nowiki> 0):_current(ptr) { | |||
iterator(node *ptr <nowiki> =</nowiki> 0):_current(ptr) { | if(_current) { | ||
if(_current) { | if(_current->right) | ||
_nodes.push_back(_current->right); | |||
if(_current->left) | |||
_nodes.push_back(_current->left); | |||
}<br> | |||
} | iterator &operator++() { | ||
_current<nowiki> =</nowiki> _nodes.back(); | |||
_nodes.pop_back();<br> | |||
if(_current) { | |||
if(_current->right) | |||
_nodes.push_back(_current->right); | |||
if(_current->left) | |||
_nodes.push_back(_current->left); | |||
} | |||
else | |||
_current<nowiki> =</nowiki> 0; | |||
} | |||
Żeby ta klasa zachowywała się jak wskaźnik dodajemy operatory: | |||
Żeby ta klasa zachowywała się jak wskaźnik dodajemy | |||
public: | public: | ||
T &operator*() {return _ptr->_val;}; | T &operator*() {return _ptr->_val;}; | ||
T *operator->() {return &(_ptr->_val);}; | T *operator->() {return &(_ptr->_val);}; | ||
Linia 122: | Linia 119: | ||
Potrzebna jest jeszcze deklaracja: | Potrzebna jest jeszcze deklaracja: | ||
friend class binary_tree; | friend class binary_tree; | ||
Żeby klasa | Żeby klasa <tt>binary_tree</tt> mogła korzystać z konstruktora | ||
<tt>iterator(node *ptr)</tt>. | |||
W klasie | W klasie <tt>binary_tree</tt> definiujemy funkcje: | ||
iterator begin() {return iterator(_root);} | iterator begin() {return iterator(_root);} | ||
iterator end(){return iterator(0);} | iterator end(){return iterator(0);} | ||
</div></div> | </div></div> | ||
{{cwiczenie|6|| | {{cwiczenie|6|| | ||
Dodaj do drzewa | Dodaj do drzewa instrukcje pozwalające na jego budowanie: | ||
iterator add_root(const T &val); | iterator add_root(const T &val); | ||
iterator add_left(iterator it, const T &val); | iterator add_left(iterator it, const T &val); | ||
iterator add_right(iterator it, const T &val); | iterator add_right(iterator it, const T &val); | ||
które tworzą nowy węzeł drzewa z | które tworzą nowy węzeł drzewa z wartością <tt>val</tt> i wstawiają go | ||
odpowiednio jako wierzchołek drzewa lub jako lewe lub prawe dziecko | odpowiednio jako wierzchołek drzewa lub jako lewe lub prawe dziecko | ||
wierzchołka wskazywanego przez | wierzchołka wskazywanego przez <tt>it</tt>. Jeśli ktoryś z tych nowych | ||
elementów już istnieje to jest rzucany wyjątek. | elementów już istnieje to jest rzucany wyjątek. | ||
Funkcje | Funkcje <tt>add</tt> zwracają iterator do nowo wstawionego elementu. | ||
'''Uwaga ''' | '''Uwaga ''' | ||
Największa trudność tego zadania leży w zwracanym iteratorze. Iterator | Największa trudność tego zadania leży w zwracanym iteratorze. Iterator | ||
ma dwie funkcje: wskazywać na dany wierzchołek drzewa oraz umożliwiać | ma dwie funkcje: wskazywać na dany wierzchołek drzewa oraz umożliwiać | ||
przechodzenie do następnego wierzchołka. W funkcjach | przechodzenie do następnego wierzchołka. W funkcjach <tt>add_...</tt> | ||
wykorzystywana jest pierwsza funkcja iteratora. Zapewnienie tego, aby | wykorzystywana jest pierwsza funkcja iteratora. Zapewnienie tego, aby | ||
zwracany iterator poprawnie implementował drugą funkcję tzn. | zwracany iterator poprawnie implementował drugą funkcję tzn. | ||
Linia 157: | Linia 153: | ||
iteratora realizującego przeszukiwanie drzewa "wszerz" nie jest | iteratora realizującego przeszukiwanie drzewa "wszerz" nie jest | ||
możliwe w czasie <math>\displaystyle O(1)</math>. Proponowane rozwiązanie to rodzielenie tych | możliwe w czasie <math>\displaystyle O(1)</math>. Proponowane rozwiązanie to rodzielenie tych | ||
dwu funkcji iteratora, i wprowadzenie klasy | dwu funkcji iteratora, i wprowadzenie klasy <tt>trivial_iterator</tt>. | ||
Klasa | Klasa <tt>trivial iterator</tt> jest modelem konceptu <tt>TrivialIterator</tt> i | ||
realizuje tylko dostęp do wierzchołka. Natomiast klasa | realizuje tylko dostęp do wierzchołka. Natomiast klasa <tt>iterator} | ||
dziedziczy z | dziedziczy z <tt>trival_iterator</tt> i uzupełnia go o funkcję | ||
przechodzenia do następnego elementu. Definicje funkcji | przechodzenia do następnego elementu. Definicje funkcji <tt>add_...</tt> zmienimy na: | ||
trivial_iterator add_root(const T &val); | trivial_iterator add_root(const T &val); |
Wersja z 10:04, 25 wrz 2006
Ćwiczenie 1
Napisz testy sprawdzające działanie szablonu inteligentnego wskaźnika opartego na zliczaniu referencji.
Ćwiczenie 2
Napisz klasę wytyczną do wskaźnika ref_ptr
opartą o
listę referencji.
Ćwiczenie 3
Napisz klasę wytyczną do wskaźnika ref_ptr
opartą o licznik we wskazywanym obiekcie. Załóż, że obiekty wskazywane dziedziczą z klasy:
class Handle { private: size_t _count; public: Handle():_count(0){}; void add_ref() { ++_count;} bool remove_ref() {--_count; return _count == 0;} size_t count() const {return _count;}; };
Ćwiczenie 4
Zaimplementuj iterator pozwalający wkładać wartości na koniec kontenera.
Ćwiczenie 5
Zaimplementuj iterator realizujący przechodzenie po drzewie binarnym. Drzewo oparte jest o strukturę:
template<typename T> class binary_tree { class node { T _val; node *_left; node *_right; } node* _root; }
Iterator powinien realizować przechodzenie drzewa "wgłąb", zaczynając od lewej strony. Dodaj do drzewa odpowiednie instrukcje begin i end, zwracające iteratory na początek i za koniec drzewa.
Ćwiczenie 6
Dodaj do drzewa instrukcje pozwalające na jego budowanie:
iterator add_root(const T &val); iterator add_left(iterator it, const T &val); iterator add_right(iterator it, const T &val);
które tworzą nowy węzeł drzewa z wartością val i wstawiają go odpowiednio jako wierzchołek drzewa lub jako lewe lub prawe dziecko wierzchołka wskazywanego przez it. Jeśli ktoryś z tych nowych elementów już istnieje to jest rzucany wyjątek. Funkcje add zwracają iterator do nowo wstawionego elementu.
Uwaga Największa trudność tego zadania leży w zwracanym iteratorze. Iterator ma dwie funkcje: wskazywać na dany wierzchołek drzewa oraz umożliwiać przechodzenie do następnego wierzchołka. W funkcjach add_... wykorzystywana jest pierwsza funkcja iteratora. Zapewnienie tego, aby zwracany iterator poprawnie implementował drugą funkcję tzn. prawidłowo określał swój następnik, jest dużo bardziej skomplikowane. Da się to zrobić dla rozpatrywanego iteratora, ale już np. dla iteratora realizującego przeszukiwanie drzewa "wszerz" nie jest możliwe w czasie . Proponowane rozwiązanie to rodzielenie tych dwu funkcji iteratora, i wprowadzenie klasy trivial_iterator.
Klasa trivial iterator jest modelem konceptu TrivialIterator i realizuje tylko dostęp do wierzchołka. Natomiast klasa iterator} dziedziczy z trival_iterator i uzupełnia go o funkcję przechodzenia do następnego elementu. Definicje funkcji add_... zmienimy na:
trivial_iterator add_root(const T &val); trivial_iterator add_left(trivial_iterator it, const T &val); trivial_iterator add_right(trivial_iterator it, const T &val);