[C++] Classe lista e iteratori
Sto provando ad implementare una classe per una lista doppiamente concatenata. Per il momento l'ho impostata nel seguente modo:
Sembra funzionare bene, ma vorrei nascondere l'implementazione della classe e facilitarne l'utilizzo da parte dell'utente. Ho pensato quindi ad un ulteriore classe che permetta all'utente di non avere a che fare con nodi e puntatori; facendo alcune ricerche ho visto che esistono degli strumenti simili messi a disposizione dalla STL chiamati iteratori, ma non ho capito bene come sono implementati.
La mia idea è quella di creare una classe iteratore che abbia come membro un puntatore a nodo e tra i vari metodi ci potrebbe essere per esempio l'overload dell'operatore ++.
A quel punto metodi della classe list che hanno come argomento (vedi "add_prima") o ritornano (vedi "trova") un puntatore a nodo avranno come argomento o ritorneranno un iteratore. E' la strada giusta oppure il tutto andrebbe impostato diversamente?
template <typename T> struct nodo { T dato; nodo<T> *precedente; nodo<T> *successivo; }; template <typename T> class lista { private: nodo<T> *testa; nodo<T> *coda; unsigned int dim; public: lista(const unsigned int& = 0, const T& = 0); lista(const lista&); ~lista(); void add_testa(const T& = 0); void add_coda(const T& = 0); void add_prima(nodo<T>*&, const T& = 0); void add_dopo(nodo<T>*&, const T& = 0); void remove_testa(); void remove_coda(); void remove_(nodo<T>*&); void ridimensiona(const unsigned int&); unsigned int get_dim(); nodo<T>* trova(const T&); nodo<T>* inizio(); nodo<T>* fine(); lista& operator =(const lista&); T& operator[](const unsigned int&); };
Sembra funzionare bene, ma vorrei nascondere l'implementazione della classe e facilitarne l'utilizzo da parte dell'utente. Ho pensato quindi ad un ulteriore classe che permetta all'utente di non avere a che fare con nodi e puntatori; facendo alcune ricerche ho visto che esistono degli strumenti simili messi a disposizione dalla STL chiamati iteratori, ma non ho capito bene come sono implementati.
La mia idea è quella di creare una classe iteratore che abbia come membro un puntatore a nodo e tra i vari metodi ci potrebbe essere per esempio l'overload dell'operatore ++.
A quel punto metodi della classe list che hanno come argomento (vedi "add_prima") o ritornano (vedi "trova") un puntatore a nodo avranno come argomento o ritorneranno un iteratore. E' la strada giusta oppure il tutto andrebbe impostato diversamente?
Risposte
L'idea è quella giusta. Se implementi le funzioni [tt]begin[/tt] and [tt]end[/tt] puoi a quel punto usare il nuovo ciclo for per iterare sulla tua lista.
#ifndef LISTA_H_INCLUDED #define LISTA_H_INCLUDED namespace tizio { template <typename T> struct nodo { T dato; nodo<T> *precedente; nodo<T> *successivo; }; template <typename T> class lista; template <typename T> class iteratore { friend class lista<T>; private: nodo<T> *indirizzo; public: iteratore(); iteratore(const iteratore<T>&); T get_dato(); bool good(); template <typename TT> friend bool operator ==(const iteratore<TT>&, const iteratore<TT>&); template <typename TT> friend bool operator !=(const iteratore<TT>&, const iteratore<TT>&); iteratore<T>& operator ++(); iteratore<T> operator ++(int); iteratore<T>& operator --(); iteratore<T> operator --(int); iteratore& operator =(const iteratore&); }; template <typename T> class lista { private: nodo<T> *testa; nodo<T> *coda; unsigned int dim; public: lista(const unsigned int& = 0, const T& = 0); lista(const lista&); ~lista(); void add_testa(const T& = 0); void add_coda(const T& = 0); void add_prima(iteratore<T>&, const T& = 0); void add_dopo(iteratore<T>&, const T& = 0); void remove_testa(); void remove_coda(); void remove_(iteratore<T>&); void ridimensiona(const unsigned int&); unsigned int get_dim(); iteratore<T> inizio(); iteratore<T> fine(); iteratore<T> trova(const T&); lista& operator =(const lista&); T& operator[](const unsigned int&); }; // le varie funzioni... } #endif // LISTA_H_INCLUDED
Esempio di utilizzo della classe:
#include <iostream> #include "lista.h" using namespace std; using namespace tizio; int main() { lista<int> l; iteratore<int> it; for(unsigned int i = 1; i < 10; ++i) { l.add_coda(i % 2 * i); } while(true) { it = l.trova(0); if(it.good()) { l.remove_(it); } else { break; } } for(it = l.inizio(); it != l.fine(); ++it) { cout << it.get_dato() << " "; } }
Va bene il modo in cui ho impostato la classe iteratore e le modifiche apportate alla classe lista?
E il modo in cui sfrutto le classi nel main?
Come posso fare per "nascondere" la struct nodo? La soluzione a cui ho pensato è quella di un secondo namespace che racchiuda la struct (in modo da poter riutilizzare l'identificatore nodo nel main), ma sembra non funzionare...
Non sono sicuro di aver compreso la tua domanda. Un modo per nascondere qualcosa è ovviamente inserirla come membro privato di un'altra. In questo caso particolare, puoi ad esempio dichiarare la classe nodo all'interno della classe lista. La classe iteratore ha a questo punto bisogno di essere una classe friend della lista in modo da poter accedere alla dichiarazione di nodo. Ma sinceramente non vedo granché ragioni per complicarti la vita. Non c'è in effetti alcun reale vantaggio nel farlo.
In effetti... mi limiterò allora a rendere privati i membri della classe nodo e a non utilizzare "using namespace tizio" nel main in modo che l'identificatore "nodo" sia disponibile.
Dovrei aggiungere qualche metodo che potrebbe rivelarsi utile nell'utilizzo delle classi lista e iteratore?
Dovrei aggiungere qualche metodo che potrebbe rivelarsi utile nell'utilizzo delle classi lista e iteratore?
Come ti ho scritto nel precedente post, la principale ragione per definire un iteratore è quella di usare le funzioni e le classi delle librerie standard e usare il nuovo for loop per la tua classe. È quindi importante usare gli stessi nomi delle funzioni rese disponibili dagli iteratori delle classi standard (come std::list).