42
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 dell’Automazione

Elementi di programmazione ad oggetti a. a. 2009/2010

Embed Size (px)

DESCRIPTION

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 dell’Automazione. Lezione 10.1. La libreria standard: i contenitori. Contenitori. - PowerPoint PPT Presentation

Citation preview

Elementi di programmazione

ad oggetti

a. a. 2009/2010

Corso di Laurea Magistrale in Ingegneria ElettronicaDocente: Mauro Mazzieri, Dipartimento di Ingegneria Informatica, Gestionale e dell’Automazione

Lezione 10.1La 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

L’ordine degli elementi non dipende dal loro valore

Nei contenitori associativi l’ordine 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 <vector>#include <list>

Contenitori sequenziali vector

Vettore con dimensione variabile Ottimizzato rispetto all’accesso indicizzato agli elementi

list Lista con collegamenti bidirezionali Ottimizzata rispetto all’inserimento 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

listend

begin

Vector

Il vector è il più semplice contenitore sequenziale

Definito nell’header vector#include <vector>

Un vector è definito come template di classe, pertanto va istanziato con un tipo: vector<int> ivec; vector<string> svec; vector<Impiegato> impiegati;

Vector: costruttori vector<T> v1;

Vuoto È il costruttore più usato

vector<T> v2(v1); Costruttore per copia

vector<T> 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<T>

vector<T> v4(7, i); Contiene 7 elementi di tipo T inizializzati a i

i deve essere di tipo T vector<T> v5(i1, i2);

Inizializza il vettore con una copia degli elementi compresi tra l’iteratore i1 e l’iteratore i2

Dimensione di vector I vector sono concepiti per avere una

dimensione che cresce dinamicamente, man mano che si aggiungono oggetti

Sono l’equivalente dinamico degli array monodimensionali

È possibile avere un vettore di vettori, es.vector< vector<int> > v; È indispensabile lo spazio attorno al

parametro di tipo vector<vector<int>> v;causerebbe un errore di compilazione, >> è un operatore!

Operazioni sui vettori v.empty( ) v.size( )

Numero di elementi Il suo tipò è vector<T>::size_tipe

v.push_back(t) Aggiunge t in coda

v[n] Restituisce l’elemento in posizione n È un Lvalue Non avviene nessun controllo sull’indice!!

La funzione membro at() invece solleva un’eccezione 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 dell’elemento

Iteratori Un iteratore definisce un metodo

generale per accedere in successione a ciascun elemento di un contenitore

Gli iteratori ridefiniscono l’operatore di deferenziazione *, il che consente di usarli come fossero puntatori *i è un Lvalue (es. *i = t; è valido) e serve

ad accedere all’elemento i++ è l’iteratore si riferisce all’elemento

successivo i==j è vero se i e j si riferiscono allo

stesso oggetto

Iteratore Tutti i contenitori della libreria standard

definiscono un iteratore vector<T>::iterator i;

Gli iteratori ridefiniscono gli operatori deferenziazione *, consente l’uso 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 l’ultimo 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<int> number_vector(numbers, numbers + nnumbers);

Aritmetica degli iteratori Per gli iteratori è definita un’aritmetica,

come per i puntatori i++, i--

Si riferisce all’elemento 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<T>::difference_type A differenza di vector<T>::size_type, è un valore

con segno È definita solo per gli iteratori ad accesso

casualeTramite l’aritmetica degli iteratori è possibile inizializzare

un iteratore direttamente in una posizione desideratavector<T>::iterator i = v.begin() + v.size() / 2;

Intervallo di iteratori Un intervallo è definito tramite due

iteratori Il primo si riferisce al primo elemento

dell’intervallo Il secondo si riferisce ad un elemento dopo

l’ultimo Intervalli inclusivi a sinistra, in matematica li

indicheremmo come [inizio, fine) Deve essere possibile raggiungere la fine

dell’intervallo incrementando successivamente il riferimento alla’inizio dell’intervallo

Se l’inizio e la fine dell’intervallo coincidono, l’intervallo è vuoto

Per iterare sull’intervallo compreso tra first e last, è lecito un ciclo del tipowhile (first != last) { // fai qualcosa con *first first++;}

Iterare sugli elementi di un vettore Usando gli indici

for (vector<T>::size_type i = 0; i < v.size(); i++) // fai qualcosa con v[i]

Usando un iteratore for (vector<T>::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<T>::const_iterator i;

L’iteratore costante va usato quando si usano, ma non si modificano, gli elementi del vettore for (vector<T>::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 dell’iteratore può essere cambiato, ma non può essere usato l’iteratore per cambiare il valore riferito

const vector<T>::iterator i = v.begin(); L’iteratore non può essere cambiato, ma l’elemento riferito sì! … è decisamente inutile

Un vector costante può avere solo iteratori costanti const vector<T> v;

conts vector<T>::iterator i = v.begin(); // errore!!!vector<T>::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 dell’elemento 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 l’ultimo

iteratore al rovescio riferito all’ultimo 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 l’inserimento 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 l’accesso 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

L’inserimento 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 un’operazione dispendiosa

c.insert(i, t) inserimento prima dell’iteratore i

c.insert(i, n, t) inserisce n elementi prima dell’iteratore i

c.insert(i, b, e) inseresce prima dell’iteratore 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

l’elemento 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, è l’ultimo elemento

corrisponde a *--c.end() c[n]

l’elemento di indice n solo per vector e deque

c.at(n) l’elemento di indice n solleva l’eccezione out_of_range se

l’indice non è valido solo per vector e deque

Rimozione di elementi c.erase(i)

rimuove l’elemento a cui fa riferimento l’iteratore 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 l’ultimo 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 dall’iteratore i di questa list (senza copiarli)

l1.splice(i, l2, e) muove l’elemento *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 l’ordine 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 : stacktemplate< typename T, typename C = deque<T> >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: queuetemplate<typename T, typename C = deque<T> >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_queuetemplate<typename T, typename C = vector<T>, typename Cmp = less<typename C::value_type> > 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<typename In> 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<T>

insieme multiset<T>

insieme con possibilità di valori ripetuti map<K, T>

mappa associativa Ad un oggetto di tipo T è associata una chiave

di tipo K Data la chiave è possibile accedere

direttamente all’elemento

multimap<K, T> mappa associativa con possibilità di

chiavi ripetute

map

map

pair tipo generico che identifica una coppia

definito come template<class First, class Second> class pair { //… }

es. pair<string, int> 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<const key_type, mapped_type>

key_compare Il tipo del criterio di confronto

Contenitori associativi: operazioni m[k]

Restituisce l’elemento il cui valore della chiave è k Solo per contenitori con chiave unica

m.find(k) Cerca l’elemento 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<string, int> m;// …map<string, int>::iterator i = m.begin();cout << i->first; // stampa la chiavecout << i->second; // stampa il valore

Aggiungere elementi Si può usare l’operatore [] per aggiungere

elementi usare un valore della chiava che non è già

presente ha l’effetto di aggiungere un elemento esempio

map<string, int> 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<K, T>, dove K è il tipo della chiave e T è il tipo dell’elemento Es.

map<string, int> rubrica;string s;// …int i = rubrica[s];

Non possono essere presenti due elementi con la stessa chiave

L’accesso 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 dell’insieme

multiset

set che consentono la presenza di più elementi uguali

accesso in tempo logaritmico a qualsiasi elemento