Classi Oggetti e classi La classe è un modello (un contenitore) che definisce la forma di un oggetto, specificando: • Come è fatto (i dati) • Quali operazioni si possono fare con- e sull’oggetto (le funzioni) Esempio: Un “libro” è una classe, che descrive un insieme di oggetti che hanno ognuno le loro specificità, ma che hanno una descrizione e degli usi in comune. “Guerra e Pace” è un oggetto che appartiene alla classe di oggetti “libro” Forma di una classe: membri In C++ la classe è una struttura dati che viene definita attraverso la parola riservata class: class Libro{ private: int ISSBN; string Titolo; string Autore; string CasaEd; public: } Questo codice crea una classe che si chiama Libro. Tutti gli oggetti che appartengono a questa classe, contengono al loro interno le informazioni su titolo, autore, casa editrice, codice ISSBN Membri pubblici e privati class Libro{ private: int ISSBN; string Titolo; string Autore; string CasaEd; public: }; Attenzione al punto e virgola! “private:” indica che i dati e le funzioni che seguono sono privati della classe, cioè non vi si può accedere se non dall’interno della classe. “public:” indica che i dati e le funzioni che seguono sono pubblici: sono accessibili a qualsiasi parte del programma Creazione di oggetti di una classe Una volta definita una classe, questa si comporta come un qualsiasi altro tipo di dato. Per creare un oggetto della classe Libro dovrò scrivere: #include “Libro.h” // Nel file “Libro.h” devo avere la definizione della classe Libro #include <iostream> using namespace std; int main() { // Dichiaro una variabile “iliade” che appartiene alla classe libro Libro iliade; // Dichiaro una variabile “odissea” che appartiene alla classe libro Libro odissea; return 0; }; Accesso ai membri di una classe Si ha accesso ai membri di una classe attraverso l’operatore “.” class Libro{ private: int ISSBN; string Autore; string CasaEd; int main() { Libro odissea; cout << odissea.Titolo << “\n”; cout << odissea.Autore <<“\n”; public: string Titolo; }; return 0; } Cosa c’è che non va? Accesso ai membri di una classe Si ha accesso ai membri di una classe attraverso l’operatore “.” class Libro{ private: int ISSBN; string Autore; string CasaEd; int main() { Libro odissea; cout << odissea.Titolo << “\n”; cout << odissea.Autore <<“\n”; public: string Titolo; }; return 0; } OK: Titolo è un membro pubblico della classe Libro. NO! Autore è un membro privato della classe Libro. Aggiunta di funzioni membro Una funzione membro deve avere almeno il prototipo definito all’interno della struttura della classe. class Libro{ private: int ISSBN; string Titolo; string Autore; string CasaEd; public: string GetTitolo(); }; GetTitolo è dichiarata come una funzione membro della classe Libro. Non ha nessun parametro di ingresso e deve restituire una stringa. Aggiunta di funzioni membro Per definire il corpo di una funzione membro ho due scelte: 1) scrivere il codice del corpo concomitantemente alla dichiarazione 2) scrivere il codice separatamente Aggiunta di funzioni membro: soluzione 1 class Libro{ private: int ISSBN; string Titolo; string Autore; string CasaEd; public: string GetTitolo() {return Titolo; } }; Va bene per le funzioni che contengono poche istruzioni Aggiunta di funzioni membro: soluzione 2 class Libro{ private: int ISSBN; string Titolo; string Autore; string CasaEd; public: string GetTitolo(); }; string Libro::GetTitolo() { return Titolo; } Aggiunta di funzioni membro: soluzione 2 string Libro::GetTitolo() { return Titolo; } L’operatore “::” serve a indicare che la funzione che sto definendo è membro della classe Libro. Se scrivessi la funzione: string GetTitolo() { return Titolo; } Essa verrebbe interpretata come la definizione di una generica funzione GetTitolo(), e non della funzione membro della classe Libro. Funzioni di interfaccia Le funzioni pubbliche che servono per dare accesso ai membri privati di una classe a dati e funzioni esterni alla classe (non-membri), si chiamano funzioni di interfaccia. class Libro{ private: int ISSBN; string Titolo; string Autore; string CasaEd; public: string GetTitolo() { return Titolo; } void SetTitolo(string s){ Titolo=s; } string GetAutore() { return Autore; } void SetAutore(string s){ autore=s; } }; Alcune funzioni di interfaccia Accesso alle funzioni membro Allo stesso modo dei dati membro di una classe, si effettua l’accesso alle funzioni membro: int main() { Libro odissea; odissea.SetTitolo(“Odissea”); odissea.SetAutore(“Omero”); cout << odissea.GetTitolo(); return 0; } Accesso i membri da puntatori Se ho un puntatore ad un oggetto, posso accedere ai membri dell’oggetto tramite l’operatore “->” int main() { Libro odissea; Libro *pl; pl=&odissea; pl->SetTitolo(“Odissea”); pl->SetAutore(“Omero”); cout << pl->GetTitolo(); return 0; } Costruttore di default di una classe Un costruttore è una funzione che inizializza un oggetto di una classe nel momento in cui viene creato. L’istruzione: Libro odissea; invoca automaticamente il costruttore di default della classe libro, che costruisce l’oggetto odissea, senza inizializzarne il contenuto. Costruttore di una classe La forma generale con cui posso definire un costruttore è: nome_classe() { …. } In questo modo posso definire esplicitamente il comportamento del costruttore Costruttore class Libro{ private: int ISSBN; string Titolo; string Autore; string CasaEd; public: Libro(); }; Costruttore Libro::Libro() { ISSBN=0; Titolo=“”; Autore=“”; CasaEd=“”; } Costruttori parametrizzati Posso definire dei costruttori che accetti dei parametri di ingresso, in modo da inizializzare i dati membri della classe a dei valori particolari: class Libro{ private: …….. public: Libro(int cod, string tit, string aut, string ed) …….. }; Libro::Libro(int cod, string tit, string aut, string ed) { ISSBN=cod; Titolo=tit; Autore=aut; CasaEd=ed; } Invocazione dei costruttori parametrizzati Ci sono due modi per invocare il costruttore visto: 1) int main() { Libro odissea(0,”Odissea”,”Omero”,”BUR”); return 0; } 2) int main() { Libro odissea = Libro(0,”Odissea”,”Omero”,”BUR”) return 0; } Distruttore Quando un oggetto di una classe esce dal campo di visibilità in cui è stato definito, deve essere eliminato, e la memoria occupata deve essere liberata. La funzione che si occupa di cancellare un oggetto è il distruttore, ed è l’esatto complementare del costruttore. La forma generale è: ~nome_classe() { …. } Es.: Classe biblioteca Se vogliamo definire una classe Biblioteca, una possibilità è utilizzare la possibilità di allocare dinamicamente una array di oggetti Libro. #include “Libro.h” // definizione della classe libro class Biblioteca{ public: Libro *collezione; public: Biblioteca(); Biblioteca(int dim); }; Biblioteca::Biblioteca(int dim) { collezione = new Libro[dim]; } Es.: Classe biblioteca #include <iostream> #include “Libro.h” // definizione della classe libro #include “Biblioteca.h” // definizione della classe biblioteca using namespace std; int main() { int dim; cout << “Qual’e’ il numero di libri da inserire?”; cin >> dim; Creo una biblioteca di dimensione dim Scrivo i titoli di alcuni libri presenti nella biblioteca Biblioteca b(dim); (b[0]).SetTitolo=“Odissea”; (b[1]).SetTitolo=“Iliade”; (b[2]).SetTitolo=“Lisistrata”; return 0; } La funzione termina e invoca i distruttori per le variabili dichiarate al suo interno Biblioteca b Libro * collezione collezione[0] collezione[1] collezione[dim-] collezione[dim-1] ISSBN 0 Titolo: Odissea Autore: Omero CasaEd: BUR b Invocazione del distruttore di Biblioteca ISSBN Libro * collezione Il contenuto di b viene cancellato: L’indirizzo contenuto in collezione viene cancellato, ma tutto ciò a cui punta no! collezione[0] collezione[1] collezione[dim-] collezione[dim-1] 0 Titolo: Odissea Autore: Omero CasaEd: BUR Memory leakage Se riservo dello spazio in memoria con new int *a; a=new int[dim]; a a[0] a[1] a[dim-1] e poi il programma cancellasse solo il puntatore all’array ma non l’array, otterrei che la memoria occupata dall’array rimane tale, ma il programma non potrebbe più accedervi, avendo eliminato il puntatore. a=0; a a[0] a[1] a[dim-1] Memory leakage Nei casi in cui si sia riservata della memoria, ma non si sia più in grado di liberarla (ad es. si è cancellato o modificato il puntatore a quall’area di memoria), si parla di memory leakage Definizione del distruttore di Biblioteca Per non generare del memory leakage quando fosse invocato il distruttore della classe Biblioteca, Bisogna fare in modo che l’intero array di oggetti Libro sia cancellato. Bisogna dunque ridefinire il distruttore: #include “Libro.h” // definizione della classe libro class Biblioteca{ public: ….. ~Biblioteca() }; Biblioteca::~Biblioteca() { delete [] collezione; } Biblioteca come lista L’utilizzo di array per gestire una collezione di oggetti come una biblioteca non è molto agevole. Quandunque dovessi aggiungere o eliminare libri dovrei ridimensionare l’array di libri, o almeno gestire i buchi che si creerebbero nell’array. Una soluzione è quella di utilizzare oggetti che siano legati l’uno all’altro, cioè che contengano l’informazione se dopo un libro ne esista un altro nella biblioteca oppure no Biblioteca come lista Biblioteca Libro 1 Libro 2 Libro 3 0 Modifica della classe Libro per l’utilizzo in una lista class Libro{ private: ….. public: Libro *pnext; }; class Biblioteca{ private: ….. public: Libro *collezione; }; Con la semplice aggiunte alla classe Libro di un puntatore a Libro, ottengo la possibilità di creare una lista Gestione della lista: aggiunta di un oggetto 1 L’idea è di inserire un nuovo oggetto all’inizio della lista, semplicemente aggiustando i puntatori collezione della Biblioteca, e pnext dell’oggetto da inserire Biblioteca Libro 1 Libro nuovo 0 Libro 2 Libro 3 0 Gestione della lista: aggiunta di un oggetto 2 L’idea è di inserire un nuovo oggetto all’inizio della lista, semplicemente aggiustando i puntatori collezione della Biblioteca, e pnext dell’oggetto da inserire Biblioteca Libro 1 Libro nuovo Libro 2 Libro 3 0 Gestione della lista: aggiunta di un oggetto 3 L’idea è di inserire un nuovo oggetto all’inizio della lista, semplicemente aggiustando i puntatori collezione della Biblioteca, e pnext dell’oggetto da inserire Biblioteca Libro 1 Libro nuovo Libro 2 Libro 3 0 Gestione della lista: aggiunta di un oggetto 4 void Biblioteca::AddLibro(Libro *pl) { pl->next=Biblioteca.collezione; Biblioteca.collezione=pl; } Gestione della lista: eliminazione di un oggetto 1 Anche per eliminare un oggetto vorrei semplicemente spostare dei puntatori, ma quello che manca è la conoscenza dell’oggetto che precede quello che voglio eliminare. Devo dunque prima trovare questo, poi operare il cambio dei puntatori. Libro da eliminare Biblioteca Libro 1 Libro 2 Libro 3 0 Gestione della lista: eliminazione di un oggetto 2 Anche per eliminare un oggetto vorrei semplicemente spostare dei puntatori, ma quello che manca è la conoscenza dell’oggetto che precede quello che voglio eliminare. Devo dunque prima trovare questo, poi operare il cambio dei puntatori. Biblioteca Libro 1 Ricerca del precedente Libro 2 Libro 3 0 Gestione della lista: eliminazione di un oggetto 3 Anche per eliminare un oggetto vorrei semplicemente spostare dei puntatori, ma quello che manca è la conoscenza dell’oggetto che precede quello che voglio eliminare. Devo dunque prima trovare questo, poi operare il cambio dei puntatori. Biblioteca Libro 1 Libro 2 Aggiornamento del puntatore Libro 3 0 Gestione della lista: eliminazione di un oggetto 4 Anche per eliminare un oggetto vorrei semplicemente spostare dei puntatori, ma quello che manca è la conoscenza dell’oggetto che precede quello che voglio eliminare. Devo dunque prima trovare questo, poi operare il cambio dei puntatori. Biblioteca Libro 1 Libro 2 Eliminazione dell’oggetto Libro 3 0 Gestione della lista: eliminazione di un oggetto 5 Libro* Biblioteca::CercaPrec(Libro *pl) { Libro *pcur=collezione; if(pcur==NULL) return 0; while((pcur->next)!=pl || occur->next==NULL) pcur=pcur->next; return pcur; } void Biblioteca::DelLibro(Libro *pl) { Libro *pprec; pprec=CercaPrec(pl); if(pprec!=NULL) { pprec->next=pl->next; delete pl; } } Lista concatenata Un modo ancora più semplice di gestire le liste è di utilizzare per ogni oggetto sia un puntatore all’elemento successivo, che uno all’elemento precedente. Biblioteca Libro 1 Libro 2 Libro 3 0 Strutture dati: la coda La coda è una struttura di dati sequenziali in cui sono definite due funzioni: 1) enqueue : inserimento nella coda 2) dequeue : estrazione dalla coda che devono essere costruite in modo tale che gli elementi in coda vengono estratti nell’ordine con cui sono stati inseriti. Coda FIFO (first in – first out) Strutture dati: lo stack Lo stack (pila) è una struttura di dati sequenziali in cui sono definite due funzioni: 1) push : inserimento nello stack 2) pop : estrazione dallo stack che devono essere costruite in modo tale che gli elementi in coda vengono estratti nell’ordine inverso in cui sono stati inseriti. Stack LIFO (last in – first out) E’ possibile pensare allo stack come ad un tubo di palle da tennis. Quando inserisco la prima, essa rotola sul fondo. Se ne inserisco una seconda, essa rotolerà sulla prima. In tal modo le prime palle inserite sono bloccate nel tubo fino a che non siano rimosse tutte le palle inserite successivamente.