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!