Fondamenti di Informatica 2
Ingegneria Informatica
Docente: Giovanni Macchia
a.a. 2002-2003
Classi ed Oggetti
Il C++ è un linguaggio di programmazione che
fornisce gli strumenti per la programmazione
ad oggetti.
In C++ le unità fondamentali sono le classi.
Le classi implementano gli ADT.
Gli oggetti sono istanze delle classi. Vengono
create considerando le classi come il loro tipo
base.
Classi ed Oggetti
La definizione di classe avviene tramite la
parola chiave class e la forma è simile alla
struct, di cui rappresenta l’evoluzione:
class nome_classe {
public:
variabili e funzioni pubbliche;
private:
variabili e funzioni private;
protected:
variabili e funzioni protette;
};
Classi ed Oggetti
Le variabili e funzioni definite dopo la parola
chiave public e prima dell’inizio della parola
chiave successiva sono accessibili da altre parti
del programma.
Le variabili e funzioni definite dopo la parola
chiave private e prima dell’inizio della parola
chiave successiva sono nascoste ad altre parti
del programma e possono essere usate solo da
funzioni membro della classe.
Vedremo più avanti il significato della parola
chiave protected.
Le parole chiave protected, public e private si
chiamano specificatori di accesso ai membri
Classi ed Oggetti
Es:
class Persona {
private:
int stipendio;
public:
char cognome[30];
char indirizzo[30];
};
Persona Ugo;
// si istanzia un oggetto avente
// come tipo dato la classe Persona;
Altre parti del programma possono accedere a
Ugo.cognome e Ugo.indirizzo ma non
direttamente a Ugo.Stipendio
Classi ed Oggetti
E’ possibile per altre parti del programma
accedere e/o manipolare i membri protetti
tramite le funzioni membro della classe.
Le funzioni membro di una classe definiscono il
comportamento che esibiscono gli oggetti
appartenenti a quella stessa classe. In C++
implementano i metodi dell’ OOP.
La classe è una evoluzione della struct di C. La
differenza è che i membri senza specificatore di
accesso in una classe sono privati, mentre in
una struttura sono pubblici.
Classi ed Oggetti
Es:
class counter {
public:
void reset( );
void incr ();
void decr ( );
int get( );
private:
int val;
};
funzioni membro
Classi ed Oggetti
In alcuni casi, è importante avere la possibilità
di definire dei membri di una classe comuni a
tutti gli oggetti della stessa classe.
Per fare questo, si usa il modificatore static per
dichiarare una variabile statica della classe,
ovvero condivisa da tutti gli oggetti della classe.
Es:
class counter {
public:
void reset( );
….
private:
static int val;
};
Classi ed Oggetti
E’ possibile dichiarare una funzione non
membro o una classe come friend di una
classe. In questo modo si consente alla funzione
o classe friend di accedere ai membri privati
delle classe.
Es;
class counter {
public:
friend void print(int);
……...};
Classi ed Oggetti: l’operatore ::
Una volta definiti i prototipi delle funzioni
membro, occorre scrivere il codice delle
funzioni e far riconoscere al compilatore che la
funzione è un metodo di una determinata classe.
Per questo si usa l’operatore di risoluzione di
ambito :: .
Es:
void contatore::reset ( ) { val = 0;};
void contatore::incr ( ) { val++;};
void contatore::decr ( ) { val--;};
int contatore::get ( ) { return val;};
Classi ed Oggetti: l’operatore ::
Tramite l’operatore di risoluzione di ambito il
compilatore riconosce di quale classe la
funzione membro è un metodo e distingue tra
metodi aventi lo stesso nome ma appartenenti a
classi differenti.
Quando una funzione membro chiama una
funzione che appartiene alla stessa classe, può
farlo direttamente senza ricorrere all’operatore
di risoluzione di ambito.
Classi ed Oggetti:dichiarazione di oggetti
Una volta definita una classe, è possibile
istanziare un numero qualsiasi di oggetti
appartenenti alla classe. Questi oggetti saranno
distinti tra loro, avranno la stessa
rappresentazione e potranno usare gli stessi
metodi forniti dalla classe.
Es:
contatore c1, c2;
definisce due oggetti c1 e c2 di tipo contatore
ognuno dei quali ha la propria copia di val e
può eseguire solo le operazioni definite nella
classe contatore.
Classi ed Oggetti: accesso ai membri
Per usare i membri di un oggetto si usano
l’operatore freccia e l’operatore punto con le
stesse modalità usate per le struct.
Es:
c1.reset( );
Questa istruzione ha il significato di invio di
messaggio all’oggetto c1 di eseguire
l’operazione reset( ) che azzera la variabile
privata val.
Un oggetto può inoltre accedere al proprio
indirizzo tramite un puntatore di nome this
Classi ed Oggetti: Costruttori e Distruttori
Nella maggior parte dei casi occorre
inizializzare alcune parti dell’oggetto prima che
siano utilizzate. Il C++ consente agli oggetti di
inizializzarsi al momento della loro creazione
tramite i costruttori.
Un costruttore è una funzioni membro pubblica
di una classe che ha le seguenti caratteristiche
• ha lo stesso nome della classe di
appartenenza
• non deve avere un tipo di ritorno (neanche
void)
• può avere argomenti di default
Classi ed Oggetti: Costruttori e Distruttori
Es:
class counter {
public:
counter (int n=0) {val = n; };
void reset( );
void incr ();
void decr ( );
int get( );
private:
int val;
};
Costruttore
Classi ed Oggetti: Costruttori e Distruttori
I costruttori sono invocati automaticamente
quando gli oggetti vengono istanziati.
Es:
contatore c1, c2(6);
Quando viene eseguita l’istruzione sopra
descritta, c1 viene creato con val = 0 (valore di
default) mentre c2 viene istanziato con val = 6.
Il costruttore viene invocato quando viene
allocata memoria all’oggetto:
contatore *p;
//il costruttore non è eseguito
p = new contatore(2) ; //il costruttore è eseguito
Classi ed Oggetti: Costruttori e Distruttori
Se un costruttore ammette argomenti non di
default, questi devono essere specificati nella
dichiarazione dell’oggetto.
Es.
class counter {
public:
counter (int n, int p) {val = n; sem = p; };
…
private:
int val,sem};
counter c1(2,0), c2(6,3), c3(7,4);
Classi ed Oggetti: Costruttori e Distruttori
Il distruttore è complementare al costruttore e
viene usato ogni volta che un oggetto viene
distrutto, cosa che avviene quando si esce
dall’ambito dell’oggetto.
Un distruttore è una funzioni membro pubblica
di una classe che ha le seguenti caratteristiche
• ha lo stesso nome della classe di
appartenenza preceduto dal carattere ~.
• non deve avere un tipo di ritorno (neanche
void)
• non può avere argomenti
Classi ed Oggetti: Costruttori e Distruttori
Es:
class counter {
public:
counter (int n=0) {val = n; };
~counter ( )
Distruttore
{cout << “contatore distrutto\n”; };
void reset( );
void incr ();
void decr ( );
int get( );
private:
int val;
};
Classi ed Oggetti: Costruttori e Distruttori
Un esempio della necessità di distruttore si ha
quando un oggetto viene rilasciato ma non tutta
la memoria occupata da esso viene rilasciata
Es:
class stringa {
char *str;
public:
stringa (char *str) {str = new char[80]; };
set (char *str);
show( );
};
Quando viene rilasciato un oggetto di tipo
stringa, l’area di memoria creata con new non è
rilasciata e non è più disponibile sull’heap!!!!
Classi ed Oggetti: Costruttori e Distruttori
Modificando la classe introducendo un
distruttore si ha
class stringa {
char *str;
public:
stringa (char *str) {str = new char[80]; };
~stringa( ) {if (str != NULL) delete[ ] str; }
set (char *str);
show( );
};
e si risolve il problema, poiché viene rilasciata
l’area di memoria heap tramite la delete[ ].
Classi ed Oggetti: Costruttori e Distruttori
• Un oggetto può essere raggruppato in array
Es:
contatore cont[4];
• Si può accedere ad un oggetto tramite
puntatore. Per un oggetto vale l’aritmetica dei
puntatori, l’operatore &, l’operatore di
dereferenza, l’operatore punto, l’operatore
freccia
•I reference a oggetti sono possibili come i
reference a variabili
• si possono passare degli oggetti a funzioni
come qualsiasi altra variabile e le funzioni
possono restituire un oggetto
Classi ed Oggetti: Costruttori e Distruttori
Vi sono comunque dei problemi:
1) quando un oggetto viene passato ad una
funzione ne viene fatta una copia bit a bit che
viene data al parametro della funzione. Se
l’oggetto contiene un puntatore a una locazione
di memoria allocata dinamicamente, allora la
copia punterà alla medesima regione di
memoria, ed ogni modifica si rifletterà
sull’originale. L’oggetto originale ne viene
pertanto influenzato.
Classi ed Oggetti: Costruttori e Distruttori
Oggetto stringa
in main()
Copia dell’oggetto
stringa nel
passaggio
a funzione
Area di memoria
per str
Classi ed Oggetti: Costruttori e Distruttori
2) quando un funzione restituisce un oggetto, il
compilatore genera automaticamente un oggetto
temporaneo contenente il valore restituito alla
funzione. Quando il valore viene restituito alla
funzione chiamante, l’oggetto temporaneo va
fuori ambito e provoca la chiamata al
distruttore, che potrebbe eliminare qualcosa di
necessario alla routine chiamante, come della
memoria allocata dinamicamente.
Classi ed Oggetti: Costruttori e Distruttori
Oggetto stringa
restituito dalla
funzione
Oggetto temporaneo
stringa contenente il
valore dell’oggetto
stringa restituito dalla
funzione e sottoposto
all’azione del distruttore
Area di memoria
per str
Classi ed Oggetti: Costruttori e Distruttori
La risoluzione a questo tipo di problema è il
costruttore di copia. Con il costruttore di
copia, è possibile specificare con precisione
cosa accade quando :
• un oggetto viene usato per inizializzarne un
altro in un’istruzione di dichiarazione
• un oggetto viene passato come parametro a
una funzione
• viene creato un oggetto temporaneo da usare
come valore restituito ad una funzione
Classi ed Oggetti: Costruttori e Distruttori
La forma più comune per il prototipo del
costruttore di copia è la seguente:
nome_classe (const nome_classe &);
Es:
class stringa {
char *str;
public:
stringa(char *, int =40); //costruttore normale
stringa(const stringa &,int =40); //costruttore di copia
char *getcar( ){ return str;}
};
Classi ed Oggetti: Costruttori e Distruttori
stringa::stringa(char *inp, int SIZE=40)
{ int i;
str = new char[SIZE];
cout << "sono qui \n";
for(i=0; *(inp+i) != '\0';i++)
str[i]=inp[i];
str[i]= '\0';
};
stringa::stringa(const stringa &ob,int SIZE=40)
{ int i;
str = new char[SIZE];
for(i=0;ob.str[i]!='\0';i++)
str[i]=ob.str[i];
str[i]='\0';
};
Classi ed Oggetti: Costruttori e Distruttori
void display(stringa ob)
{
for(int i=0;*(ob.getcar()+i) != '\0';i++)
cout << *(ob.getcar()+i);
cout << "\n";
};
main ( )
{
stringa a("PIPPO25");
display(a); //viene invocato il costruttore di copia
return 0;
}
Classi ed Oggetti: Overload di operatori
E’ possibile effettuare l’overload degli operatori del
C++ in relazione ai tipi di classe. E’ possibile, per
esempio, effettuare un overload dell’operatore + e
fargli eseguire delle operazioni definite dal
programmatore.
Il concetto di overload è pertanto strettamente
legato all’overloading delle funzioni. La forma
generale di una funzione operatore utilizza la parola
chiave operator nel seguente modo
tipo nome_classe::operator op(argomenti);
dove op è l’operatore di cui si vuole effettuare
l’overloading.
Classi ed Oggetti: Overload di operatori
Es:
class parall {
int x,y,z;
public:
parall operator=(parall);
…..};
parall parall::operator=(parall ob)
{parall tmp;
tmp.x = ob.x;
tmp.y= ob.y;
tmp z = ob.z;
return tmp; }
Classi ed Oggetti: Overload di operatori
Es:
parall a, b;
…..
b=a; //viene usato l’operatore = di parall
L’operatore = effettua l’overloading dell’operatore
= standard in C++. Anche altri operatori
(p.e. &&, ||) standard in C++ possono essere
ridefiniti.
Classi ed Oggetti: Composizione
La composizione in C++ avviene specificando gli
oggetti componenti (oggetti membro) all’interno
della classe di cui fanno parte.
Occorre comunque indicare gli argomenti del
costruttore della classe composta da passare ai
costruttori degli oggetti membro
Questo avviene tramite gli inizializzatori di
membro, che vengono usati nella definizione del
costruttore.
Classi ed Oggetti: Composizione
Es:
class Data:
{Data (int, int, int);
…};
Impiegato::Impiegato( char *nome, int giorno, int
mese, int anno)
: datadinascita(int giorno, int mese, int anno)
{…};
Se una classe Impiegato ha un oggetto membro
datadinascita di classe Data, i parametri giorno,
mese e anno saranno passati al costruttore Data
Classi ed Oggetti: Ereditarietà
Nella terminologia del C++, la superclasse si
chiama classe base, mentre la sottoclasse si chiama
classe derivata. Le modalità di derivazione di una
classe da una classe base sono specificate tramite lo
specificatore di accesso alla classe base e la forma
generale per dichiarare che una classe eredita da
un’altra è:
class classe_derivata: accesso classe base {
corpo della classe_derivata}
dove accesso può essere private, public e protected
Classi ed Oggetti: Ereditarietà
Es:
class impiegato {
public:
Impiegato (char *) ;
~Impiegato( );
void print( ) const;
private:
char *nome;};
class interinale: public impiegato {
public:
…..
void print( ) const;
private:
double orario;
double salario;
};
Classi ed Oggetti: Ereditarietà
• Quando una classe base viene ereditata come
pubblica, tutti suoi membri pubblici diventano
pubblici per la classe derivata mentre i suoi membri
privati sono nascosti alla classe derivata.
• Quando una classe base viene ereditata come
privata, tutti i suoi membri pubblici diventano
membri privati della classe derivata mentre i suoi
membri privati sono nascosti alla classe derivata
Classi ed Oggetti: Ereditarietà
Ricordiamo che i membri di una classe possono
essere private, public e protected.
La modalità protected è simile alla private , con una
differenza che si riscontra nel caso di classe
derivata: quando una classe base viene ereditata
come pubblica, tutti suoi membri protected
diventano membri protetti per la classe derivata;
quando una classe base viene ereditata come
privata, tutti suoi membri protected diventano
membri privati per la classe derivata
In questo modo è possibile dar vita a membri che
sono privati per la loro classe ma che sono
ereditabili ed accessibili per una classe derivata.
Classi ed Oggetti: Ereditarietà
Es:
class base {
public:
void set(int) ;
...
protected:
int i, j; // privato per base, ma accessibile per derivato
};
class derivato: public base {
public:
…
// può accedere a i e j di base
private:
int k;
};
Classi ed Oggetti: Ereditarietà
Quando una classe base viene ereditata come
protected, tutti suoi membri pubblici e protetti
diventano protetti per la classe derivata mentre i
suoi membri privati sono nascosti alla classe
derivata.
Classi ed Oggetti: Ereditarietà
Una classe derivata eredita i membri dalla classe
base e pertanto , quando si istanzia un oggetto di
classe derivata, il C++ chiama dapprima il
costruttore della classe base e successivamente il
costruttore della classe derivata.
L’esecuzione dei distruttori avviene in ordine
inverso: dapprima vengono eseguiti i distruttori
della classe derivata e successivamente quelli della
classe base.
Classi ed Oggetti: Ereditarietà
Nel caso in cui i costruttori della classe base
necessitino di parametri, nella dichiarazione dei
costruttori delle classi derivate si utilizzano gli
inizializzatori di membro :
Es:
Interinale::Interinale (char *nome):
impiegato(nome)
{
….
}
Classi ed Oggetti: Ereditarietà
E’ possibile che una classe erediti le proprietà di più
classe basi. In questo caso, si ha la eredità multipla
(multiple inheritance).
In questo caso la forma generale per dichiarare una
classe con multiple inheritance è
class classe_derivata: accesso1 classe_base1,
accesso2 classe_base2,..accesson classe_basen {
corpo della classe_derivata}
dove accesso1,..,accesson possono essere private,
public e protected
Classi ed Oggetti: Ereditarietà
Sono possibili alcune ambiguità nei casi di eredità
multipla:
class a {public: int i};
class b: public a {public: int j};
class c: public a {public: int k};
class d: public b, public c { int sum};
d ob;
ob.i = 5; // istruzione ambigua: quale i ??
A quale i si riferisce? A quella di b o c ?? Si
introduce un elemento di ambiguità.
Classi ed Oggetti: Ereditarietà
Per risolvere questi tipo di ambiguità , si usano le
classi base virtuali.
La loro semantica è simile alle classi base non
virtuali, con in aggiunta la parola chiave virtual
prima delle definizione di accesso.
Es:
class a {public: int i};
class b: virtual public a {public: int j};
In questo modo, quando due o più oggetti sono
derivati da una classe base comune, è possibile
evitare che in un oggetto derivato da quegli oggetti
siano presenti più copie della classe base.
Classi ed Oggetti: Polimorfismo
Il polimorfismo viene implementato tramite le
funzioni virtuali.
Una funzione virtuale si intende una funzione che
viene preceduta dalla parola chiave virtual in una
classe base e poi ridefinita in una o più classi
derivate.
Classi ed Oggetti: Polimorfismo
Es:
class a {
..
public: virtual void print ( ) {cout << “prima stampa”;}
};
class b: public a {
..
public: void print ( ) {cout << “seconda stampa”;}
};
Classi ed Oggetti: Polimorfismo
Ciascuna classe derivata può quindi avere la propria
versione di funzione virtuale, pur mantenendo la
medesima interfaccia.
Quando una classe derivata non ridefinisce una
funzione virtuale, la funzione viene impiegata nel
modo in cui è definita nella classe base
Scarica

Classi ed Oggetti