Relazione tra tipi e classi astratte, cenni sulla STL

Introduzione alla STL
Corso di
Linguaggi di Programmazione ad Oggetti 1
a cura di:
Giancarlo Cherchi
Introduzione
Cosa s’intende per STL?
STL ? Standard Template Library
l
l
E’ una libreria di programmazione nata nei
laboratori Hewlett Packard di Palo Alto in
California ed è stata sviluppata da Alexander
Stepanov e Meng Lee
Agevola il programmatore nella scrittura di
codice generico e fa un intenso uso dei template
Introduzione
l
l
Esistono algoritmi logicamente identici che non
dipendono da una particolare implementazione
delle strutture dati su cui operano ma solo da
alcune proprietà semantiche
E’ possibile astrarli dalla particolare
implementazione in modo da non perdere in
efficienza
Introduzione
l
l
l
l
l
l
STL è una libreria costituita da 5 componenti
fondamentali:
Algoritmo: procedura computazionale in grado di
lavorare su contenitori differenti
Contenitore: oggetto che contiene e gestisce altri oggetti
Iteratore: astrazione dell’algoritmo di accesso al
contenitore (in modo che un algoritmo possa lavorare su
contenitori diversi)
Oggetto Funzione: classe che ha definito l’operatore di
chiamata di funzione (operator())
Adattore: incapsula un componente per fornire un’altra
interfaccia
Contenitori
l
l
l
l
Esistono vari tipi di contenitori, che differiscono
per il modo in cui sono organizzati (e/o ordinati)
gli elementi al loro interno
Le principali categorie di contenitori sono due:
Contenitori Sequenza: gestiscono un insieme
finito di oggetti dello stesso tipo, seguendo un
ordinamento strettamente lineare
Contenitori Associativi: danno la possibilità di
accedere rapidamente ai dati contenuti tramite le
chiavi (keys)
Sommario Contenitori
Contenitori Sequenza
Vector
List
Deque (Double Ended Queue)
Contenitori Associativi
Set
Map
Multiset
Multimap
Vector
l
l
l
l
l
E’ una potente generalizzazione dell’array del C, in grado
di espandere dinamicamente le proprie dimensioni
La STL astrae dallo specifico modello utilizzato per la
gestione della memoria mediante la classe Allocator.
Ciascun contenitore ha infatti un allocatore come
parametro per consentire l’impiego di un gestore di
memoria personalizzato
Ne esiste comunque uno di default che è adeguato per la
maggior parte delle esigenze!
La maggior parte delle funzioni membro dei contenitori
hanno nome e semantica comuni tra i vari contenitori
della STL
Uso di Vector
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v;
cout << v.max_size(); // dimensione max
cout << v.size(); // dimensione attuale
}
Inserimento/Estrazione
l
l
l
Esistono metodi per inserire/eliminare/accedere
agli elementi di un vettore
E’ garantito che l’inserimento/cancellazione di
un elemento alla fine di un vector richiede un
tempo costante (ammortizzata), mentre le stesse
operazioni effettuate in posizione centrale
richiedono un tempo lineare
Ricordiamoci infatti che il vector espande
dinamicamente le sue dimensioni e il tempo
richiesto per copiare i dati risulta proporzionale
al numero di elementi in esso contenuti
Complessità
Contenitore
Al
centro
Alla fine
Vector
Inserimento/
Cancellazione
all’inizio
Lineare
Lineare
List
Costante
Costante
Costante
ammortizzata
(*)
Costante
Deque
Costante
ammortizzata
Lineare
Costante
ammortizzata
(*) il tempo può variare ampiamente ma il tempo totale per una sequenza
di N operazioni è sicuramente inferiore rispetto a N volte il caso
peggiore
Inserimento/Estrazione
l
l
l
l
Se si istanzia un oggetto vector usando il
costruttore di default, non viene allocata nessuna
memoria per contenere elementi
Lo si può verificare con il metodo capacity()
indicante il numero di elementi allocati in
memoria che su un vettore appena creato rende il
valore zero
Verrà quindi ridimensionato al primo
inserimento di un elemento
E’ comunque possibile specificare la dimensione
del vettore in fase di costruzione
Un esempio…
#include <vector>
#include <iostream>
using namespace std;
main() {
vector<int> v;
cout << v.capacity(); // zero elementi!
vector<float> w(100);
cout << w.capacity(); // 100 elementi
}
Un esempio…
vector<int> v;
v.push_back(3); // inserisce un elemento in coda
cout << v.capacity(); /* dipende
dall’implementazione dell’allocator usata! */
v.push_back(4); // inserisco un altro elemento
cout << v.size(); // ora il vettore ha 2 elementi
cout << v[0] << “ “ << v[1]; // accedo agli elem.!
Iteratori
l
l
l
Sono un generalizzazione di puntatori in grado di
lavorare in modo uniforme su strutture dati di
tipo diverso
Come i puntatori si dereferenziano tramite
l’operatore * e possono essere incrementati
mediante l’operatore ++
Sono associati a dei contenitori e puntano ad un
oggetto del tipo presente nel contenitore stesso
Iteratori
l
l
Due iteratori molto importanti sono restituiti dai
metodi begin() ed end() presenti in tutte le
implementazioni dei contenitori STL.
Se v è un vettore,
v.begin() rende un iteratore che punta al primo
elemento del vettore
v.end() rende un iteratore che punta NON
all’ultimo elemento (come potrebbe sembrare)
ma subito dopo di esso e NON si deve mai
deferenziare!
Iteratori
l
L’iteratore restituito dal metodo end() si usa
soltanto per verificare se si è raggiunta la fine
del vettore:
vector<int> v(3);
v[0] = 5;
v[1] = 2;
v[2] = 7;
vector<int>::iterator first = v.begin();
vector<int>::iterator last = v.end();
while (first != last)
cout << *first++ << " ";
Iteratori
l
l
Tramite l’uso degli iteratori è possibile separare
gli algoritmi dalle specifiche implementazioni
dei contenitori.
Ad, esempio è possibile sfruttare uno degli
algoritmi della STL per ordinare un vettore o una
sua parte:
vector<int> v;
sort ( v.begin(), v.end() );
Operazioni tipiche
l
l
E’ possibile copiare un vettore su un altro,
confrontare dei vettori, scambiare dei vettori,
inserire un vettore in un altro, assegnare un
valore iniziale a tutti gli elementi…
vector<int> v(10, 3); // v ha dieci elementi
inizializzati a 3
vector<int> w = v;
w.push_back(7);
v = w; //
cout << v.empty(); // false (0)
Operazioni tipiche
w.insert (w.end(), v.begin(), v.end() );
// inserisce alla fine di w tutti gli elementi di v
v.insert (v.begin(), 2, 5);
// inserisce in testa a w due copie dell’oggetto
“5”
v.pop_back(); // cancella l’ultimo elemento di v
v.erase ( v.begin() ); // cancella il primo elemento
v.erase (v.begin(), v.end()); // cancella tutti gli
elementi di v
Osservazioni
•
•
•
Quando si inserisce/cancella da un contenitore e
si ha un iteratore che punta ad un elemento,
questo potrebbe diventare invalido.
Rimangono validi solo gli iteratori che puntano
ad un elemento precedente a quello inserito (nel
caso di vector)
Questo perché può essere stata allocata nuova
memoria e spostati dei dati e gli iteratori non
sono aggiornati automaticamente… prestare
pertanto attenzione!