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.