Elementi di programmazione ad oggetti a. a. 2009/2010 Corso di Laurea Magistrale in Ingegneria Elettronica Docente: Mauro Mazzieri, Dipartimento di Ingegneria Informatica, Gestionale e dellAutomazione
Lezione 10.1 La libreria standard: i contenitori.
Contenitori Un contenitore è un oggetto che serve a contenere altri oggetti Nei contenitori sequenziali ogni elemento ha un indice numerico e si può accedere ad un elemento tramite la sua posizione Lordine degli elementi non dipende dal loro valore Nei contenitori associativi lordine degli elementi dipende dal valore di una chiave Gli oggetti contenuti sono tutti dello stesso tipo
Contenitori della libreria standard I contenitori sono definite come template di classe Per usare un contenitore bisogna includere il suo header, es. #include #include
Contenitori sequenziali vector Vettore con dimensione variabile Ottimizzato rispetto allaccesso indicizzato agli elementi list Lista con collegamenti bidirezionali Ottimizzata rispetto allinserimento e cancellazione di elementi Non consente di accedere ad un elemento tramite un indice, sarebbe troppo lento deque Coda bidirezionale (double-ended queue) Adattatori: stack Pila (LIFO) queue Coda (FIFO) priority_queue Coda con gestione della priorità
Contenitori sequenziali size begin Vector list end begin
Vector Il vector è il più semplice contenitore sequenziale Definito nellheader vector #include Un vector è definito come template di classe, pertanto va istanziato con un tipo: vector ivec; vector svec; vector impiegati;
Vector: costruttori vector v1; Vuoto È il costruttore più usato vector v2(v1); Costruttore per copia vector v3(5); Contiene 5 elementi di tipo T Gli elementi sono inizializzati al loro valore standard (es. 0 per gli interi) o tramite il loro costruttore di default Se non cè un costruttore di default per T, non si può usare questo costruttore per vector vector v4(7, i); Contiene 7 elementi di tipo T inizializzati a i i deve essere di tipo T vector v5(i1, i2); Inizializza il vettore con una copia degli elementi compresi tra literatore i1 e literatore i2
Dimensione di vector I vector sono concepiti per avere una dimensione che cresce dinamicamente, man mano che si aggiungono oggetti Sono lequivalente dinamico degli array monodimensionali È possibile avere un vettore di vettori, es. vector > v; È indispensabile lo spazio attorno al parametro di tipo vector > v; causerebbe un errore di compilazione, >> è un operatore!
Operazioni sui vettori v.empty( ) v.size( ) Numero di elementi Il suo tipò è vector ::size_tipe v.push_back(t) Aggiunge t in coda v[n] Restituisce lelemento in posizione n È un Lvalue Non avviene nessun controllo sullindice!! La funzione membro at() invece solleva uneccezione di tipo out_of_range se il parametro supera la dimensione del vector v1 = v2 Operatore di assegnamento v1 == v2 (! =,, >=) Operatori di confronto Si può usare un operatore relazionale solo se lo stesso operatore è definito anche per il tipo dellelemento
Iteratori Un iteratore definisce un metodo generale per accedere in successione a ciascun elemento di un contenitore Gli iteratori ridefiniscono loperatore di deferenziazione *, il che consente di usarli come fossero puntatori *i è un Lvalue (es. *i = t; è valido) e serve ad accedere allelemento i++ è literatore si riferisce allelemento successivo i==j è vero se i e j si riferiscono allo stesso oggetto
Iteratore Tutti i contenitori della libreria standard definiscono un iteratore vector ::iterator i; Gli iteratori ridefiniscono gli operatori deferenziazione *, consente luso come un puntatore Incremento ++ Uguaglianza ==, diuguaglianza != Operatore freccia, es. i->m dereferenzia i e restituisce il membro m v.begin() Restituisce un iteratore che si riferisce al primo elemento v.end() Restituisce un iteratore che si riferisce ad una posizione dopo lultimo elemento Non è un elemento valido, ma solo un valore sentinella Se il vector è vuoto, v.begin() e v.end() coincidono
Comè fatto un iteratore? Un modo ragionevole di rappresentare un iteratore per un vettore è usare un puntatore ma potrebbe essere anche definito come una coppia (puntatore, offset), che consentirebbe di controllare per una lista potrebbe essere un puntatore ad un collegamento Al di là della loro effettiva definizione, ciò che conta è che gli iteratori hanno degli operatori con una semantica comune N.B. I puntatori sono utilizzabili iteratori su vettori! Es. int numbers[] = { 1, 3, 5, 7, 11 }; size_t nnumbers = sizeof(numbers) / sizeof(int); vector number_vector(numbers, numbers + nnumbers);
Aritmetica degli iteratori Per gli iteratori è definita unaritmetica, come per i puntatori i++, i-- Si riferisce allelemento successivo o precedente Sono definiti per gli iteratori bidirezionali i + 5, i -3, i += 7, i -= 9 Un certo numero di elementi più avanti o più indietro Sono definiti per gli iteratori ad accesso casuale i – j È di tipo vector ::difference_type A differenza di vector ::size_type, è un valore con segno È definita solo per gli iteratori ad accesso casuale Tramite laritmetica degli iteratori è possibile inizializzare un iteratore direttamente in una posizione desiderata vector ::iterator i = v.begin() + v.size() / 2;
Intervallo di iteratori Un intervallo è definito tramite due iteratori Il primo si riferisce al primo elemento dellintervallo Il secondo si riferisce ad un elemento dopo lultimo Intervalli inclusivi a sinistra, in matematica li indicheremmo come [inizio, fine) Deve essere possibile raggiungere la fine dellintervallo incrementando successivamente il riferimento allainizio dellintervallo Se linizio e la fine dellintervallo coincidono, lintervallo è vuoto Per iterare sullintervallo compreso tra first e last, è lecito un ciclo del tipo while (first != last) { // fai qualcosa con *first first++; }
Iterare sugli elementi di un vettore Usando gli indici for (vector ::size_type i = 0; i < v.size(); i++) // fai qualcosa con v[i] Usando un iteratore for (vector ::iterator i = v.begin(); i != v.end(); i++) // fai qualcosa con *i Quando si modifica la dimensione di un vettore (es. con push_back), gli iteratori esistenti non sono più validi.
Iteratore costante Tutti i contenitori della libreria standard definiscono un iteratore costante vector ::const_iterator i; Literatore costante va usato quando si usano, ma non si modificano, gli elementi del vettore for (vector ::const_iterator i = v.begin(); i != v.end(); i++) cout << *i; Usare *i come se fosse un Lvalue causa un errore di compilazione Il valore delliteratore può essere cambiato, ma non può essere usato literatore per cambiare il valore riferito const vector ::iterator i = v.begin(); Literatore non può essere cambiato, ma lelemento riferito sì! … è decisamente inutile Un vector costante può avere solo iteratori costanti const vector v; conts vector ::iterator i = v.begin(); // errore!!! vector ::const_iterator j = v.end(); // Ok
typedef in vector size_type iterator const_iterator reverse_iterator const_reverse_iterator difference_type value_type reference const_reference tipo numerico senza segno in grado di contenere la dimensione del più grande contenitore possibile iteratore iteratore che non consente di modificare gli elementi iteratore che scorre gli elementi in ordine inverso iteratore che scorre gli elementi in ordine inverso e non consente di modificare gli elementi tipo numerico con segno in grado di contenere la differenza tra due iteratori il tipo dellelemento value_type& const value_type&
iteratori in vector v.begin() v.end() v.rbegin() r.rend() iteratore riferito al primo elemento iteratore riferito ad un elemento dopo lultimo iteratore al rovescio riferito allultimo elemento iteratore al rovescio riferito ad un elemento prima del primo di ognuno cè la versione const e la versione non const
Altri contenitori sequenziali Deque Coda bidirezionale: permette linserimento di elementi in testa o in coda in tempo costante Inserire o rimuovere elementi nel mezzo comporta spostare elementi a destra o a sinistra Permetto laccesso indicizzato agli elementi List Lista bidirezionale: ogni elemento conosce il precedente ed il successivo Per accedere ad un elemento occorre attraversare tutti gli elementi precedenti Linserimento o la rimozione di un elemento in un punto qualsiasi richiede un tempo costante
Aggiungere elementi ad un contenitore sequenziale c.push_back(t) inserimento in coda c.push_front(t) inserimento in testa (solo list e deque) per i vector si può usare v.insert(v.begin(), t) ma si tratta di unoperazione dispendiosa c.insert(i, t) inserimento prima delliteratore i c.insert(i, n, t) inserisce n elementi prima delliteratore i c.insert(i, b, e) inseresce prima delliteratore i gli elementi della sequenza compresa tra b ed e
Aggiungere elementi ad un contenitore sequenziale Aggiungere un elemento ad un contenitore significa copiarlo Non cè nessuna relazione tra t e lelemento nel contenitore dopo che è stato inserito Gli inserimenti in un vector o deque invalidano gli iteratori Sicuramente quelli che si riferiscono ad elementi dopo quello inserito end() è sempre invalidato
Dimensione di un contenitore c.size() restituisce il numero di elementi c.max_size() restituisce un valore di tipo c::size_type che rappresenta il numero massimo di elementi che il contenitore può contenere c.empty() restituisce un bool che indica se c.size() è 0 o no c.resize(n) imposta la dimensione del contenitore a n elementi se n < c.size() gli elementi in eccesso vengono scartati se si devono aggiungere nuovi elementi, prendono il valore di default invalida gli iteratori c.resize(n, t) imposta la dimensione del contenitore ad n elementi se si devono aggiungere nuovi elementi, assumono valore t
Capienza di un contenitore Un vettore cresce dinamicamente per far spazio ai nuovi elementi inseriti Viene allocato più spazio di quello utilizzato dagli elementi attuali, per v.capacity() restituisce il numero di elementi che possono essere inseriti senza dover allocare della memoria v.reserve(n) impone che la capacità sia almeno pari ad n può esseere utile ad es. v.reserve(50) prima di fare un ciclo che chiama 50 volte push_back()
Accesso agli elementi c.front() se c non è vuoto, è il primo elemento corrisponde a *c.end() c.back() se c non è vuoto, è lultimo elemento corrisponde a *--c.end() c[n] lelemento di indice n solo per vector e deque c.at(n) lelemento di indice n solleva leccezione out_of_range se lindice non è valido solo per vector e deque
Rimozione di elementi c.erase(i) rimuove lelemento a cui fa riferimento literatore i c.erase(b, e) rimuove la sequenza di elementi compresa tra gli iteratori b ed e c.clear() rimuove tutti gli elementi pop_back Rimuove lultimo elemento pop_front Rimuove il primo elemento Solo per list e deque (non per vector)
Operazioni su list l1.sort() ordinamento crescente l1.splice(i, l2) sposta i valori della list l2 a partire dalliteratore i di questa list (senza copiarli) l1.splice(i, l2, e) muove lelemento *e dalla lista l2 prima di i in questa lis (senza copiarlo) l1.merge(l2) fusione di liste ordinate l1.unique() rimuove gli elementi duplicati l1.remove(v) rimuove tutte le istanze di v l1.reverse() inverte lordine della list
deque tipi e operatori di vector… … più operazioni su testa front() push_front() pop_front() una coda a due capi, ottimizzata per operazioni che si svolgono alle estremità
Adattatori per sequenze : stack template > class stack { protected: C c; public: typedef typename C::value_type value_type; typedef typename C::reference reference; typedef typename C::const_reference const_reference; typedef typename C::size_type size_type; typedef C container_type; explicit stack(const C& __c = C()) : c(__c) {} bool empty() const { return c.empty(); } size_type size() const { return c.size(); } reference top() { return c.back(); } const_reference top() const { return c.back(); } void push(const value_type& __x) { c.push_back(__x); } void pop() { c.pop_back(); } };
Adattatori per sequenze: queue template > class queue { protected: C c; public: typedef typename C::value_type value_type; typedef typename C::reference reference; typedef typename C::const_reference const_reference; typedef typename C::size_type size_type; typedef C container_type; explicit queue(const C& __c = C()) : c(__c) {} bool empty() const { return c.empty(); } size_type size() const { return c.size(); } reference front() { return c.front(); } const_reference front() const { return c.front(); } reference back() { return c.back(); } const_reference back() const { return c.back(); } void push(const value_type& __x) { c.push_back(__x); } void pop() { c.pop_front(); } };
Adattatori per sequenze: priority_queue template, typename Cmp = less > class priority_queue { protected: C c; Cmp comp; public: typedef typename C::value_type value_type; typedef typename C::reference reference; typedef typename C::const_reference const_reference; typedef typename C::size_type size_type; typedef C container_type; explicit priority_queue(const Cmp& __x = Cmp(), const C& __s = C()) : c(__s), comp(__x) { std::make_heap(c.begin(), c.end(), comp); } template priority_queue(In __first, In __last, const Cmp& __x = Cmp(), const C& __s = C()) : c(__s), comp(__x) { c.insert(c.end(), __first, __last); std::make_heap(c.begin(), c.end(), comp);} bool empty() const { return c.empty(); } size_type size() const { return c.size(); } const_reference top() const { return c.front(); } void push(const value_type& __x); void pop(); };
Contenitori associativi set insieme multiset insieme con possibilità di valori ripetuti map mappa associativa Ad un oggetto di tipo T è associata una chiave di tipo K Data la chiave è possibile accedere direttamente allelemento multimap mappa associativa con possibilità di chiavi ripetute
map
pair tipo generico che identifica una coppia definito come template class pair { //… } es. pair voce(Mauro, 12); make_pair(f, s) restituisce un pair, inferendo i parametri di tipo dai tipo di f e s p1.first è il membro pubblico corrispondente al primo elemento p1.second è il membro pubblico corrispondente al secondo elemento p1 < p2 restituisce (p1.first < p2.first) || (!(p2.first < p1.first) && p1.second < p2.second) p1==p2 resituisce (p1.first == p2.first) && (p1.first == p2.first)
Contenitori associativi: tipi membro Oltre ai tipi definiti anche dai contenitori vettoriali, i contenitori associativi definiscono key_type Il tipo della chiave mapped_type Il tipo del valore contenuto value_type presente anche per i contenitori sequenziali, nei contenitori associativi corrisponde a pair key_compare Il tipo del criterio di confronto
Contenitori associativi: operazioni m[k] Restituisce lelemento il cui valore della chiave è k Solo per contenitori con chiave unica m.find(k) Cerca lelemento con chiave k m.lower_bound(k) cerca il primo elemento con chiave k m.upper_bound(k) cerca il primo elemento con chiave maggiore di k m.equal_range(k) restituisce un pair il cui primo elemento è m.lower_bound(k) ed il secondo è m.upper_bound(k)
Contenitori associativi: iteratori Dereferenziando un iteratore di un contenitore associativo si ottiene un pair esempio: map m; // … map ::iterator i = m.begin(); cout first; // stampa la chiave cout second; // stampa il valore
Aggiungere elementi Si può usare loperatore [] per aggiungere elementi usare un valore della chiava che non è già presente ha leffetto di aggiungere un elemento esempio map frequenza; frequenza[casa] = 2; // aggiunge un elemento la cui chiave è casa e imposta il valore a 2 in alternativa, si può usare il membro insert(), es. frequenza.insert(make_pair(casa, 2)); non ha effetto se esiste già un elemento la cui chiave è casa Provare il semplice esercizio: memorizzare e stampare la frequenza delle parole lette fa un flusso di input (suggerimento: usare frequenza[parola]++).
map Contenitore associativo, permette di recuperare un elemento sulla base del suo valore map, dove K è il tipo della chiave e T è il tipo dellelemento Es. map rubrica; string s; // … int i = rubrica[s]; Non possono essere presenti due elementi con la stessa chiave Laccesso ad un elemento richiede un tempo logaritmico rispetto alla numerosità degli elementi
multimap una multimap è una map che può avere due o più elementi con la stessa chiave permette di recuperare tutti gli elementi con un certo valore della chiave accesso in tempo logaritmico
set insieme ordinato di elementi tutti gli elementi debbono essere diversi tra loro accesso in tempo logaritmico ad un qualsiasi elemento dellinsieme
multiset set che consentono la presenza di più elementi uguali accesso in tempo logaritmico a qualsiasi elemento